@open-mercato/core 0.9.1-develop.7499.1.fcec456e4e → 0.9.1-develop.7515.1.e2712c8b00
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/audit_logs/components/AuditLogsActions.js +1 -1
- package/dist/modules/audit_logs/components/AuditLogsActions.js.map +2 -2
- package/dist/modules/auth/frontend/reset.js +7 -3
- package/dist/modules/auth/frontend/reset.js.map +2 -2
- package/dist/modules/business_rules/api/rules/route.js +12 -1
- package/dist/modules/business_rules/api/rules/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +17 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +3 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +1 -0
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/components/products/productFormUtils.js +9 -0
- package/dist/modules/catalog/components/products/productFormUtils.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +32 -32
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +22 -22
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +34 -34
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/ecommerce/search.js +1 -2
- package/dist/modules/ecommerce/search.js.map +2 -2
- package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js +4 -7
- package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipping-service.js +29 -11
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/status-sync.js +2 -2
- package/dist/modules/shipping_carriers/lib/status-sync.js.map +2 -2
- package/dist/modules/shipping_carriers/workers/webhook-processor.js +60 -33
- package/dist/modules/shipping_carriers/workers/webhook-processor.js.map +3 -3
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js +10 -7
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js.map +2 -2
- package/dist/modules/warranty_claims/commands/claim-lines.js +89 -68
- package/dist/modules/warranty_claims/commands/claim-lines.js.map +2 -2
- package/dist/modules/warranty_claims/commands/claims.js +14 -13
- package/dist/modules/warranty_claims/commands/claims.js.map +2 -2
- package/dist/modules/warranty_claims/commands/shared.js +8 -2
- package/dist/modules/warranty_claims/commands/shared.js.map +2 -2
- package/dist/modules/wms/api/interceptors.js +3 -0
- package/dist/modules/wms/api/interceptors.js.map +2 -2
- package/dist/modules/wms/commands/configuration.js +16 -16
- package/dist/modules/wms/commands/configuration.js.map +2 -2
- package/dist/modules/wms/events.js +1 -0
- package/dist/modules/wms/events.js.map +2 -2
- package/dist/modules/wms/lib/catalogInventoryProfileIntent.js +9 -0
- package/dist/modules/wms/lib/catalogInventoryProfileIntent.js.map +2 -2
- package/dist/modules/wms/widgets/injection/catalog-inventory-profile/widget.js +11 -3
- package/dist/modules/wms/widgets/injection/catalog-inventory-profile/widget.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/audit_logs/components/AuditLogsActions.tsx +1 -1
- package/src/modules/auth/frontend/reset.tsx +8 -3
- package/src/modules/business_rules/api/rules/route.ts +11 -0
- package/src/modules/business_rules/i18n/de.json +1 -0
- package/src/modules/business_rules/i18n/en.json +1 -0
- package/src/modules/business_rules/i18n/es.json +1 -0
- package/src/modules/business_rules/i18n/ko.json +1 -0
- package/src/modules/business_rules/i18n/pl.json +1 -0
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +20 -1
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +2 -1
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +1 -0
- package/src/modules/catalog/components/products/productFormUtils.ts +9 -0
- package/src/modules/customers/backend/customers/companies/page.tsx +32 -32
- package/src/modules/customers/backend/customers/deals/page.tsx +22 -22
- package/src/modules/customers/backend/customers/people/page.tsx +34 -34
- package/src/modules/customers/i18n/de.json +12 -0
- package/src/modules/customers/i18n/en.json +12 -0
- package/src/modules/customers/i18n/es.json +12 -0
- package/src/modules/customers/i18n/ko.json +12 -0
- package/src/modules/customers/i18n/pl.json +12 -0
- package/src/modules/ecommerce/i18n/de.json +2 -1
- package/src/modules/ecommerce/i18n/en.json +2 -1
- package/src/modules/ecommerce/i18n/es.json +2 -1
- package/src/modules/ecommerce/i18n/ko.json +2 -1
- package/src/modules/ecommerce/i18n/pl.json +2 -1
- package/src/modules/ecommerce/search.ts +0 -1
- package/src/modules/shipping_carriers/api/webhook/[provider]/route.ts +4 -7
- package/src/modules/shipping_carriers/lib/shipping-service.ts +29 -11
- package/src/modules/shipping_carriers/lib/status-sync.ts +2 -2
- package/src/modules/shipping_carriers/workers/webhook-processor.ts +71 -39
- package/src/modules/staff/backend/staff/time-tracking/entries/page.tsx +20 -14
- package/src/modules/warranty_claims/commands/claim-lines.ts +107 -66
- package/src/modules/warranty_claims/commands/claims.ts +14 -13
- package/src/modules/warranty_claims/commands/shared.ts +7 -1
- package/src/modules/wms/api/interceptors.ts +9 -0
- package/src/modules/wms/commands/configuration.ts +28 -16
- package/src/modules/wms/events.ts +1 -0
- package/src/modules/wms/lib/catalogInventoryProfileIntent.ts +9 -0
- package/src/modules/wms/widgets/injection/catalog-inventory-profile/widget.ts +13 -4
package/.turbo/turbo-build.log
CHANGED
|
@@ -153,7 +153,7 @@ function AuditLogsActions({
|
|
|
153
153
|
const showRedo = itemCanRedo && item.executionState === "undone";
|
|
154
154
|
const canRedo = showRedo && isRedoCandidate(item);
|
|
155
155
|
if (!canUndo && !showRedo) return null;
|
|
156
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1", children: [
|
|
156
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1", "data-actions-cell": true, children: [
|
|
157
157
|
canUndo ? /* @__PURE__ */ jsx(
|
|
158
158
|
Button,
|
|
159
159
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/audit_logs/components/AuditLogsActions.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/audit_logs/extension-points'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { DataTable, type PaginationProps } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ActionLogDetailsDialog } from './ActionLogDetailsDialog'\nimport { Undo2, RotateCcw } from 'lucide-react'\nimport { markRedoConsumed, markUndoSuccess } from '@open-mercato/ui/backend/operations/store'\nimport { useAuditPermissions, canUndoEntry, canRedoEntry } from '@open-mercato/ui/backend/version-history'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('audit_logs').child({ component: 'AuditLogsActions' })\n\n/**\n * `raiseCrudError` prefers the server's `error` body over the fallback message and stamps\n * `status` on anything it builds from an HTTP response, so a numeric `status` is what\n * distinguishes a route's refusal from a client-side failure. Without that check an\n * offline or aborted request would put a raw \"Failed to fetch\" into the operator's\n * banner; those keep the generic fallback instead.\n */\nfunction resolveServerReason(err: unknown): string | undefined {\n if (!(err instanceof Error)) return undefined\n const status = (err as Error & { status?: unknown }).status\n if (typeof status !== 'number') return undefined\n const message = err.message?.trim()\n return message ? message : undefined\n}\n\nexport type ActionLogItem = {\n id: string\n commandId: string\n actionLabel: string | null\n executionState: string\n actorUserId: string | null\n actorUserName: string | null\n tenantId: string | null\n tenantName: string | null\n organizationId: string | null\n organizationName: string | null\n resourceKind: string | null\n resourceId: string | null\n undoToken: string | null\n createdAt: string\n updatedAt: string\n snapshotBefore?: unknown | null\n snapshotAfter?: unknown | null\n changes?: Record<string, unknown> | null\n context?: Record<string, unknown> | null\n}\n\nexport function AuditLogsActions({\n items,\n onRefresh,\n isLoading,\n headerExtras,\n onUndoError,\n onRedoError,\n pagination,\n}: {\n items: ActionLogItem[] | undefined\n onRefresh: () => Promise<void>\n isLoading?: boolean\n headerExtras?: React.ReactNode\n /** Receives the server's reason when it sent one, so the page can show why the undo was refused. */\n onUndoError?: (reason?: string) => void\n onRedoError?: (reason?: string) => void\n pagination?: PaginationProps\n}) {\n const t = useT()\n const permissions = useAuditPermissions(true)\n const [undoingToken, setUndoingToken] = React.useState<string | null>(null)\n const [redoingId, setRedoingId] = React.useState<string | null>(null)\n const [selected, setSelected] = React.useState<ActionLogItem | null>(null)\n const actionItems = Array.isArray(items) ? items : []\n const latestUndoable = React.useMemo(() => actionItems.find((item) => !!item.undoToken && item.executionState === 'done'), [actionItems])\n const noneLabel = t('audit_logs.common.none')\n const latestPerResource = React.useMemo(() => {\n const map = new Map<string, string>()\n let fallback: string | null = null\n for (const item of actionItems) {\n if (!item.undoToken || item.executionState !== 'done') continue\n const key = buildResourceKey(item)\n if (key) {\n if (!map.has(key)) map.set(key, item.id)\n } else if (!fallback) {\n fallback = item.id\n }\n }\n return { map, fallback }\n }, [actionItems])\n const latestUndoneId = React.useMemo(() => {\n const undone = actionItems.filter((item) => item.executionState === 'undone')\n if (!undone.length) return null\n const sorted = [...undone].sort((a, b) => {\n const aTs = Date.parse(a.updatedAt)\n const bTs = Date.parse(b.updatedAt)\n return (Number.isFinite(bTs) ? bTs : 0) - (Number.isFinite(aTs) ? aTs : 0)\n })\n return sorted[0]?.id ?? null\n }, [actionItems])\n\n const isLatestUndoableForItem = React.useCallback((item: ActionLogItem) => {\n const key = buildResourceKey(item)\n if (key) return latestPerResource.map.get(key) === item.id\n return latestPerResource.fallback === item.id\n }, [latestPerResource])\n\n const isRedoCandidate = React.useCallback((item: ActionLogItem) => item.executionState === 'undone' && latestUndoneId === item.id, [latestUndoneId])\n\n const handleUndo = React.useCallback(async (token: string | null) => {\n if (!token) return\n setUndoingToken(token)\n try {\n await apiCallOrThrow('/api/audit_logs/audit-logs/actions/undo', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ undoToken: token }),\n }, { errorMessage: t('audit_logs.error.undo') })\n markUndoSuccess(token)\n await onRefresh()\n } catch (err) {\n logger.error('Undo action failed', { err })\n onUndoError?.(resolveServerReason(err))\n } finally {\n setUndoingToken(null)\n }\n }, [onRefresh, onUndoError, t])\n\n const handleRedo = React.useCallback(async (logId: string | null) => {\n if (!logId) return\n setRedoingId(logId)\n try {\n await apiCallOrThrow('/api/audit_logs/audit-logs/actions/redo', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ logId }),\n }, { errorMessage: t('audit_logs.error.redo') })\n markRedoConsumed(logId)\n await onRefresh()\n } catch (err) {\n logger.error('Redo action failed', { err })\n onRedoError?.(resolveServerReason(err))\n } finally {\n setRedoingId(null)\n }\n }, [onRefresh, onRedoError, t])\n\n const columns = React.useMemo<ColumnDef<ActionLogItem, any>[]>(() => [\n {\n accessorKey: 'actionLabel',\n header: t('audit_logs.actions.columns.action'),\n cell: (info) => info.row.original.actionLabel || info.row.original.commandId,\n },\n {\n accessorKey: 'resourceKind',\n header: t('audit_logs.actions.columns.resource'),\n cell: (info) => formatResource(info.row.original, noneLabel),\n },\n {\n accessorKey: 'actorUserId',\n header: t('audit_logs.actions.columns.user'),\n cell: (info) => info.row.original.actorUserName || info.getValue() || noneLabel,\n meta: { priority: 3 },\n },\n {\n accessorKey: 'tenantId',\n header: t('audit_logs.actions.columns.tenant'),\n cell: (info) => info.row.original.tenantName || info.getValue() || noneLabel,\n meta: { priority: 4 },\n },\n {\n accessorKey: 'organizationId',\n header: t('audit_logs.actions.columns.organization'),\n cell: (info) => info.row.original.organizationName || info.getValue() || noneLabel,\n meta: { priority: 4 },\n },\n {\n accessorKey: 'createdAt',\n header: t('audit_logs.actions.columns.when'),\n cell: (info) => formatDate(info.getValue() as string),\n },\n {\n accessorKey: 'executionState',\n header: t('audit_logs.actions.columns.status'),\n },\n {\n id: 'controls',\n header: t('audit_logs.actions.columns.controls'),\n enableSorting: false,\n cell: (info) => {\n const item = info.row.original\n const itemCanUndo = canUndoEntry(permissions, item.actorUserId)\n const itemCanRedo = canRedoEntry(permissions, item.actorUserId)\n const canUndo = itemCanUndo && Boolean(item.undoToken) && item.executionState === 'done' && isLatestUndoableForItem(item)\n const showRedo = itemCanRedo && item.executionState === 'undone'\n const canRedo = showRedo && isRedoCandidate(item)\n if (!canUndo && !showRedo) return null\n return (\n <div className=\"flex justify-end gap-1\">\n {canUndo ? (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={t('audit_logs.actions.undo')}\n onClick={() => { void handleUndo(item.undoToken) }}\n disabled={undoingToken === item.undoToken || Boolean(redoingId)}\n >\n <Undo2 className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n ) : null}\n {showRedo ? (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={t('audit_logs.actions.redo')}\n onClick={() => { void handleRedo(item.id) }}\n disabled={!canRedo || redoingId === item.id || Boolean(undoingToken)}\n >\n <RotateCcw className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n ) : null}\n </div>\n )\n },\n meta: { align: 'right' },\n },\n ], [t, noneLabel, handleUndo, handleRedo, isLatestUndoableForItem, isRedoCandidate, undoingToken, redoingId, permissions])\n\n const undoButton = latestUndoable?.undoToken && canUndoEntry(permissions, latestUndoable.actorUserId) ? (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n onClick={() => { void handleUndo(latestUndoable.undoToken) }}\n disabled={Boolean(undoingToken) || Boolean(redoingId)}\n >\n {undoingToken ? t('audit_logs.actions.undoing') : t('audit_logs.actions.undo')}\n </Button>\n ) : null\n\n const combinedActions = undoButton || headerExtras\n ? <div className=\"flex items-center gap-2\">{headerExtras}{undoButton}</div>\n : undefined\n\n const showSelfOnlyHint = !permissions.isLoading && !permissions.canViewTenant && !!permissions.currentUserId\n\n return (\n <>\n {showSelfOnlyHint ? (\n <Alert status=\"information\" className=\"mb-4\">\n <AlertDescription>\n {t('audit_logs.hint.view_self_only', 'Showing only your own changes. Contact an administrator for broader access.')}\n </AlertDescription>\n </Alert>\n ) : null}\n <DataTable<ActionLogItem>\n title={t('audit_logs.actions.title')}\n titleHeadingLevel={2}\n data={actionItems}\n columns={columns}\n actions={combinedActions}\n perspective={{ tableId: extensionPoints.hosts.actionsTable.tableId }}\n isLoading={Boolean(isLoading) || Boolean(undoingToken) || Boolean(redoingId)}\n onRowClick={(item) => setSelected(item)}\n pagination={pagination}\n />\n {selected ? (\n <ActionLogDetailsDialog\n item={selected}\n onClose={() => setSelected(null)}\n />\n ) : null}\n </>\n )\n}\n\nfunction formatResource(item: { resourceKind?: string | null; resourceId?: string | null }, fallback: string) {\n if (!item.resourceKind && !item.resourceId) return fallback\n return [item.resourceKind, item.resourceId].filter(Boolean).join(' \u00B7 ')\n}\n\nfunction buildResourceKey(item: { resourceKind?: string | null; resourceId?: string | null }) {\n const kind = typeof item.resourceKind === 'string' ? item.resourceKind.trim() : ''\n const id = typeof item.resourceId === 'string' ? item.resourceId.trim() : ''\n if (!kind && !id) return null\n return `${kind}::${id}`\n}\n\nfunction formatDate(value: string) {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return value\n return new Intl.DateTimeFormat(undefined, {\n dateStyle: 'medium',\n timeStyle: 'short',\n }).format(date)\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/audit_logs/extension-points'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { DataTable, type PaginationProps } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ActionLogDetailsDialog } from './ActionLogDetailsDialog'\nimport { Undo2, RotateCcw } from 'lucide-react'\nimport { markRedoConsumed, markUndoSuccess } from '@open-mercato/ui/backend/operations/store'\nimport { useAuditPermissions, canUndoEntry, canRedoEntry } from '@open-mercato/ui/backend/version-history'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('audit_logs').child({ component: 'AuditLogsActions' })\n\n/**\n * `raiseCrudError` prefers the server's `error` body over the fallback message and stamps\n * `status` on anything it builds from an HTTP response, so a numeric `status` is what\n * distinguishes a route's refusal from a client-side failure. Without that check an\n * offline or aborted request would put a raw \"Failed to fetch\" into the operator's\n * banner; those keep the generic fallback instead.\n */\nfunction resolveServerReason(err: unknown): string | undefined {\n if (!(err instanceof Error)) return undefined\n const status = (err as Error & { status?: unknown }).status\n if (typeof status !== 'number') return undefined\n const message = err.message?.trim()\n return message ? message : undefined\n}\n\nexport type ActionLogItem = {\n id: string\n commandId: string\n actionLabel: string | null\n executionState: string\n actorUserId: string | null\n actorUserName: string | null\n tenantId: string | null\n tenantName: string | null\n organizationId: string | null\n organizationName: string | null\n resourceKind: string | null\n resourceId: string | null\n undoToken: string | null\n createdAt: string\n updatedAt: string\n snapshotBefore?: unknown | null\n snapshotAfter?: unknown | null\n changes?: Record<string, unknown> | null\n context?: Record<string, unknown> | null\n}\n\nexport function AuditLogsActions({\n items,\n onRefresh,\n isLoading,\n headerExtras,\n onUndoError,\n onRedoError,\n pagination,\n}: {\n items: ActionLogItem[] | undefined\n onRefresh: () => Promise<void>\n isLoading?: boolean\n headerExtras?: React.ReactNode\n /** Receives the server's reason when it sent one, so the page can show why the undo was refused. */\n onUndoError?: (reason?: string) => void\n onRedoError?: (reason?: string) => void\n pagination?: PaginationProps\n}) {\n const t = useT()\n const permissions = useAuditPermissions(true)\n const [undoingToken, setUndoingToken] = React.useState<string | null>(null)\n const [redoingId, setRedoingId] = React.useState<string | null>(null)\n const [selected, setSelected] = React.useState<ActionLogItem | null>(null)\n const actionItems = Array.isArray(items) ? items : []\n const latestUndoable = React.useMemo(() => actionItems.find((item) => !!item.undoToken && item.executionState === 'done'), [actionItems])\n const noneLabel = t('audit_logs.common.none')\n const latestPerResource = React.useMemo(() => {\n const map = new Map<string, string>()\n let fallback: string | null = null\n for (const item of actionItems) {\n if (!item.undoToken || item.executionState !== 'done') continue\n const key = buildResourceKey(item)\n if (key) {\n if (!map.has(key)) map.set(key, item.id)\n } else if (!fallback) {\n fallback = item.id\n }\n }\n return { map, fallback }\n }, [actionItems])\n const latestUndoneId = React.useMemo(() => {\n const undone = actionItems.filter((item) => item.executionState === 'undone')\n if (!undone.length) return null\n const sorted = [...undone].sort((a, b) => {\n const aTs = Date.parse(a.updatedAt)\n const bTs = Date.parse(b.updatedAt)\n return (Number.isFinite(bTs) ? bTs : 0) - (Number.isFinite(aTs) ? aTs : 0)\n })\n return sorted[0]?.id ?? null\n }, [actionItems])\n\n const isLatestUndoableForItem = React.useCallback((item: ActionLogItem) => {\n const key = buildResourceKey(item)\n if (key) return latestPerResource.map.get(key) === item.id\n return latestPerResource.fallback === item.id\n }, [latestPerResource])\n\n const isRedoCandidate = React.useCallback((item: ActionLogItem) => item.executionState === 'undone' && latestUndoneId === item.id, [latestUndoneId])\n\n const handleUndo = React.useCallback(async (token: string | null) => {\n if (!token) return\n setUndoingToken(token)\n try {\n await apiCallOrThrow('/api/audit_logs/audit-logs/actions/undo', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ undoToken: token }),\n }, { errorMessage: t('audit_logs.error.undo') })\n markUndoSuccess(token)\n await onRefresh()\n } catch (err) {\n logger.error('Undo action failed', { err })\n onUndoError?.(resolveServerReason(err))\n } finally {\n setUndoingToken(null)\n }\n }, [onRefresh, onUndoError, t])\n\n const handleRedo = React.useCallback(async (logId: string | null) => {\n if (!logId) return\n setRedoingId(logId)\n try {\n await apiCallOrThrow('/api/audit_logs/audit-logs/actions/redo', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ logId }),\n }, { errorMessage: t('audit_logs.error.redo') })\n markRedoConsumed(logId)\n await onRefresh()\n } catch (err) {\n logger.error('Redo action failed', { err })\n onRedoError?.(resolveServerReason(err))\n } finally {\n setRedoingId(null)\n }\n }, [onRefresh, onRedoError, t])\n\n const columns = React.useMemo<ColumnDef<ActionLogItem, any>[]>(() => [\n {\n accessorKey: 'actionLabel',\n header: t('audit_logs.actions.columns.action'),\n cell: (info) => info.row.original.actionLabel || info.row.original.commandId,\n },\n {\n accessorKey: 'resourceKind',\n header: t('audit_logs.actions.columns.resource'),\n cell: (info) => formatResource(info.row.original, noneLabel),\n },\n {\n accessorKey: 'actorUserId',\n header: t('audit_logs.actions.columns.user'),\n cell: (info) => info.row.original.actorUserName || info.getValue() || noneLabel,\n meta: { priority: 3 },\n },\n {\n accessorKey: 'tenantId',\n header: t('audit_logs.actions.columns.tenant'),\n cell: (info) => info.row.original.tenantName || info.getValue() || noneLabel,\n meta: { priority: 4 },\n },\n {\n accessorKey: 'organizationId',\n header: t('audit_logs.actions.columns.organization'),\n cell: (info) => info.row.original.organizationName || info.getValue() || noneLabel,\n meta: { priority: 4 },\n },\n {\n accessorKey: 'createdAt',\n header: t('audit_logs.actions.columns.when'),\n cell: (info) => formatDate(info.getValue() as string),\n },\n {\n accessorKey: 'executionState',\n header: t('audit_logs.actions.columns.status'),\n },\n {\n id: 'controls',\n header: t('audit_logs.actions.columns.controls'),\n enableSorting: false,\n cell: (info) => {\n const item = info.row.original\n const itemCanUndo = canUndoEntry(permissions, item.actorUserId)\n const itemCanRedo = canRedoEntry(permissions, item.actorUserId)\n const canUndo = itemCanUndo && Boolean(item.undoToken) && item.executionState === 'done' && isLatestUndoableForItem(item)\n const showRedo = itemCanRedo && item.executionState === 'undone'\n const canRedo = showRedo && isRedoCandidate(item)\n if (!canUndo && !showRedo) return null\n return (\n <div className=\"flex justify-end gap-1\" data-actions-cell>\n {canUndo ? (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={t('audit_logs.actions.undo')}\n onClick={() => { void handleUndo(item.undoToken) }}\n disabled={undoingToken === item.undoToken || Boolean(redoingId)}\n >\n <Undo2 className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n ) : null}\n {showRedo ? (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={t('audit_logs.actions.redo')}\n onClick={() => { void handleRedo(item.id) }}\n disabled={!canRedo || redoingId === item.id || Boolean(undoingToken)}\n >\n <RotateCcw className=\"size-4\" aria-hidden=\"true\" />\n </Button>\n ) : null}\n </div>\n )\n },\n meta: { align: 'right' },\n },\n ], [t, noneLabel, handleUndo, handleRedo, isLatestUndoableForItem, isRedoCandidate, undoingToken, redoingId, permissions])\n\n const undoButton = latestUndoable?.undoToken && canUndoEntry(permissions, latestUndoable.actorUserId) ? (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n onClick={() => { void handleUndo(latestUndoable.undoToken) }}\n disabled={Boolean(undoingToken) || Boolean(redoingId)}\n >\n {undoingToken ? t('audit_logs.actions.undoing') : t('audit_logs.actions.undo')}\n </Button>\n ) : null\n\n const combinedActions = undoButton || headerExtras\n ? <div className=\"flex items-center gap-2\">{headerExtras}{undoButton}</div>\n : undefined\n\n const showSelfOnlyHint = !permissions.isLoading && !permissions.canViewTenant && !!permissions.currentUserId\n\n return (\n <>\n {showSelfOnlyHint ? (\n <Alert status=\"information\" className=\"mb-4\">\n <AlertDescription>\n {t('audit_logs.hint.view_self_only', 'Showing only your own changes. Contact an administrator for broader access.')}\n </AlertDescription>\n </Alert>\n ) : null}\n <DataTable<ActionLogItem>\n title={t('audit_logs.actions.title')}\n titleHeadingLevel={2}\n data={actionItems}\n columns={columns}\n actions={combinedActions}\n perspective={{ tableId: extensionPoints.hosts.actionsTable.tableId }}\n isLoading={Boolean(isLoading) || Boolean(undoingToken) || Boolean(redoingId)}\n onRowClick={(item) => setSelected(item)}\n pagination={pagination}\n />\n {selected ? (\n <ActionLogDetailsDialog\n item={selected}\n onClose={() => setSelected(null)}\n />\n ) : null}\n </>\n )\n}\n\nfunction formatResource(item: { resourceKind?: string | null; resourceId?: string | null }, fallback: string) {\n if (!item.resourceKind && !item.resourceId) return fallback\n return [item.resourceKind, item.resourceId].filter(Boolean).join(' \u00B7 ')\n}\n\nfunction buildResourceKey(item: { resourceKind?: string | null; resourceId?: string | null }) {\n const kind = typeof item.resourceKind === 'string' ? item.resourceKind.trim() : ''\n const id = typeof item.resourceId === 'string' ? item.resourceId.trim() : ''\n if (!kind && !id) return null\n return `${kind}::${id}`\n}\n\nfunction formatDate(value: string) {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return value\n return new Intl.DateTimeFormat(undefined, {\n dateStyle: 'medium',\n timeStyle: 'short',\n }).format(date)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@open-mercato/ui/primitives/card";
|
|
6
6
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
@@ -13,6 +13,10 @@ function ResetPage() {
|
|
|
13
13
|
const [submitting, setSubmitting] = useState(false);
|
|
14
14
|
const [error, setError] = useState(null);
|
|
15
15
|
const [fieldError, setFieldError] = useState(null);
|
|
16
|
+
const [clientReady, setClientReady] = useState(false);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setClientReady(true);
|
|
19
|
+
}, []);
|
|
16
20
|
async function onSubmit(e) {
|
|
17
21
|
e.preventDefault();
|
|
18
22
|
setError(null);
|
|
@@ -43,14 +47,14 @@ function ResetPage() {
|
|
|
43
47
|
/* @__PURE__ */ jsx(CardContent, { children: sent ? /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
44
48
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("auth.reset.sent", "If an account with that email exists, we sent a reset link. Please check your inbox.") }),
|
|
45
49
|
/* @__PURE__ */ jsx(Link, { href: "/login", className: "text-sm underline", children: t("auth.reset.backToLogin", "Back to login") })
|
|
46
|
-
] }) : /* @__PURE__ */ jsxs("form", { className: "grid gap-3", onSubmit, noValidate: true, children: [
|
|
50
|
+
] }) : /* @__PURE__ */ jsxs("form", { className: "grid gap-3", method: "post", onSubmit, noValidate: true, "data-auth-ready": clientReady ? "1" : "0", children: [
|
|
47
51
|
error && /* @__PURE__ */ jsx("div", { className: "text-sm text-status-error-text", children: error }),
|
|
48
52
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-1", children: [
|
|
49
53
|
/* @__PURE__ */ jsx(Label, { htmlFor: "email", children: t("auth.email") }),
|
|
50
54
|
/* @__PURE__ */ jsx(EmailInput, { id: "email", name: "email", required: true, "aria-invalid": !!fieldError, "aria-describedby": fieldError ? "email-error" : void 0 }),
|
|
51
55
|
fieldError && /* @__PURE__ */ jsx("p", { id: "email-error", className: "text-sm text-status-error-text", children: fieldError })
|
|
52
56
|
] }),
|
|
53
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-2 w-full", disabled: submitting, children: submitting ? "..." : t("auth.sendResetLink") }),
|
|
57
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-2 w-full", disabled: submitting || !clientReady, children: submitting ? "..." : t("auth.sendResetLink") }),
|
|
54
58
|
/* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(Link, { href: "/login", className: "text-xs text-muted-foreground underline", children: t("auth.reset.backToLogin", "Back to login") }) })
|
|
55
59
|
] }) })
|
|
56
60
|
] }) });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/auth/frontend/reset.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport { useState } from 'react'\nimport Link from 'next/link'\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { EmailInput } from '@open-mercato/ui/primitives/email-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport default function ResetPage() {\n const t = useT()\n const [sent, setSent] = useState(false)\n const [submitting, setSubmitting] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [fieldError, setFieldError] = useState<string | null>(null)\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n setFieldError(null)\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n const res = await fetch('/api/auth/reset', { method: 'POST', body: form })\n if (!res.ok) {\n const data = await res.json().catch(() => null)\n if (data?.fieldErrors?.email?.length) {\n setFieldError(t('auth.reset.errors.emailInvalid', 'Please enter a valid email address.'))\n return\n }\n setError(data?.error || t('auth.reset.error', 'Something went wrong'))\n return\n }\n setSent(true)\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.resetPassword')}</CardTitle>\n <CardDescription>{t('auth.reset.description', 'Enter your email to receive reset link')}</CardDescription>\n </CardHeader>\n <CardContent>\n {sent ? (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-muted-foreground\">\n {t('auth.reset.sent', 'If an account with that email exists, we sent a reset link. Please check your inbox.')}\n </p>\n <Link href=\"/login\" className=\"text-sm underline\">\n {t('auth.reset.backToLogin', 'Back to login')}\n </Link>\n </div>\n ) : (\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate>\n {error && <div className=\"text-sm text-status-error-text\">{error}</div>}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <EmailInput id=\"email\" name=\"email\" required aria-invalid={!!fieldError} aria-describedby={fieldError ? 'email-error' : undefined} />\n {fieldError && <p id=\"email-error\" className=\"text-sm text-status-error-text\">{fieldError}</p>}\n </div>\n <Button type=\"submit\" className=\"mt-2 w-full\" disabled={submitting}>\n {submitting ? '...' : t('auth.sendResetLink')}\n </Button>\n <div className=\"text-center\">\n <Link href=\"/login\" className=\"text-xs text-muted-foreground underline\">\n {t('auth.reset.backToLogin', 'Back to login')}\n </Link>\n </div>\n </form>\n )}\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport { useEffect, useState } from 'react'\nimport Link from 'next/link'\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { EmailInput } from '@open-mercato/ui/primitives/email-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport default function ResetPage() {\n const t = useT()\n const [sent, setSent] = useState(false)\n const [submitting, setSubmitting] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [fieldError, setFieldError] = useState<string | null>(null)\n const [clientReady, setClientReady] = useState(false)\n\n useEffect(() => {\n setClientReady(true)\n }, [])\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n setFieldError(null)\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n const res = await fetch('/api/auth/reset', { method: 'POST', body: form })\n if (!res.ok) {\n const data = await res.json().catch(() => null)\n if (data?.fieldErrors?.email?.length) {\n setFieldError(t('auth.reset.errors.emailInvalid', 'Please enter a valid email address.'))\n return\n }\n setError(data?.error || t('auth.reset.error', 'Something went wrong'))\n return\n }\n setSent(true)\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.resetPassword')}</CardTitle>\n <CardDescription>{t('auth.reset.description', 'Enter your email to receive reset link')}</CardDescription>\n </CardHeader>\n <CardContent>\n {sent ? (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-muted-foreground\">\n {t('auth.reset.sent', 'If an account with that email exists, we sent a reset link. Please check your inbox.')}\n </p>\n <Link href=\"/login\" className=\"text-sm underline\">\n {t('auth.reset.backToLogin', 'Back to login')}\n </Link>\n </div>\n ) : (\n <form className=\"grid gap-3\" method=\"post\" onSubmit={onSubmit} noValidate data-auth-ready={clientReady ? '1' : '0'}>\n {error && <div className=\"text-sm text-status-error-text\">{error}</div>}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <EmailInput id=\"email\" name=\"email\" required aria-invalid={!!fieldError} aria-describedby={fieldError ? 'email-error' : undefined} />\n {fieldError && <p id=\"email-error\" className=\"text-sm text-status-error-text\">{fieldError}</p>}\n </div>\n <Button type=\"submit\" className=\"mt-2 w-full\" disabled={submitting || !clientReady}>\n {submitting ? '...' : t('auth.sendResetLink')}\n </Button>\n <div className=\"text-center\">\n <Link href=\"/login\" className=\"text-xs text-muted-foreground underline\">\n {t('auth.reset.backToLogin', 'Back to login')}\n </Link>\n </div>\n </form>\n )}\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA+CQ,SACE,KADF;AA9CR,SAAS,WAAW,gBAAgB;AACpC,OAAO,UAAU;AACjB,SAAS,MAAM,aAAa,YAAY,WAAW,uBAAuB;AAC1E,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,YAAY;AAEN,SAAR,YAA6B;AAClC,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAwB,IAAI;AAChE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AAEpD,YAAU,MAAM;AACd,mBAAe,IAAI;AAAA,EACrB,GAAG,CAAC,CAAC;AAEL,iBAAe,SAAS,GAAqC;AAC3D,MAAE,eAAe;AACjB,aAAS,IAAI;AACb,kBAAc,IAAI;AAClB,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,OAAO,IAAI,SAAS,EAAE,aAAa;AACzC,YAAM,MAAM,MAAM,MAAM,mBAAmB,EAAE,QAAQ,QAAQ,MAAM,KAAK,CAAC;AACzE,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAC9C,YAAI,MAAM,aAAa,OAAO,QAAQ;AACpC,wBAAc,EAAE,kCAAkC,qCAAqC,CAAC;AACxF;AAAA,QACF;AACA,iBAAS,MAAM,SAAS,EAAE,oBAAoB,sBAAsB,CAAC;AACrE;AAAA,MACF;AACA,cAAQ,IAAI;AAAA,IACd,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SACE,oBAAC,SAAI,WAAU,kDACb,+BAAC,QAAK,WAAU,mBACd;AAAA,yBAAC,cACC;AAAA,0BAAC,aAAW,YAAE,oBAAoB,GAAE;AAAA,MACpC,oBAAC,mBAAiB,YAAE,0BAA0B,wCAAwC,GAAE;AAAA,OAC1F;AAAA,IACA,oBAAC,eACE,iBACC,qBAAC,SAAI,WAAU,cACb;AAAA,0BAAC,OAAE,WAAU,iCACV,YAAE,mBAAmB,sFAAsF,GAC9G;AAAA,MACA,oBAAC,QAAK,MAAK,UAAS,WAAU,qBAC3B,YAAE,0BAA0B,eAAe,GAC9C;AAAA,OACF,IAEA,qBAAC,UAAK,WAAU,cAAa,QAAO,QAAO,UAAoB,YAAU,MAAC,mBAAiB,cAAc,MAAM,KAC5G;AAAA,eAAS,oBAAC,SAAI,WAAU,kCAAkC,iBAAM;AAAA,MACjE,qBAAC,SAAI,WAAU,cACb;AAAA,4BAAC,SAAM,SAAQ,SAAS,YAAE,YAAY,GAAE;AAAA,QACxC,oBAAC,cAAW,IAAG,SAAQ,MAAK,SAAQ,UAAQ,MAAC,gBAAc,CAAC,CAAC,YAAY,oBAAkB,aAAa,gBAAgB,QAAW;AAAA,QAClI,cAAc,oBAAC,OAAE,IAAG,eAAc,WAAU,kCAAkC,sBAAW;AAAA,SAC5F;AAAA,MACA,oBAAC,UAAO,MAAK,UAAS,WAAU,eAAc,UAAU,cAAc,CAAC,aACpE,uBAAa,QAAQ,EAAE,oBAAoB,GAC9C;AAAA,MACA,oBAAC,SAAI,WAAU,eACb,8BAAC,QAAK,MAAK,UAAS,WAAU,2CAC3B,YAAE,0BAA0B,eAAe,GAC9C,GACF;AAAA,OACF,GAEJ;AAAA,KACF,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,6 +4,7 @@ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
5
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
6
6
|
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
+
import { isUniqueViolation } from "@open-mercato/shared/lib/db/pg-errors";
|
|
7
8
|
import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
8
9
|
import { BusinessRule } from "../../data/entities.js";
|
|
9
10
|
import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
|
|
@@ -235,6 +236,15 @@ async function POST(req) {
|
|
|
235
236
|
await em.persist(rule).flush();
|
|
236
237
|
await invalidateBusinessRuleDiscoveryCache(cache, rule.tenantId, rule.organizationId);
|
|
237
238
|
} catch (error) {
|
|
239
|
+
if (isUniqueViolation(error)) {
|
|
240
|
+
return NextResponse.json(
|
|
241
|
+
{
|
|
242
|
+
error: t("business_rules.errors.duplicateRuleId", { ruleId: parsed.data.ruleId }),
|
|
243
|
+
fieldErrors: { ruleId: t("business_rules.errors.duplicateRuleId", { ruleId: parsed.data.ruleId }) }
|
|
244
|
+
},
|
|
245
|
+
{ status: 409 }
|
|
246
|
+
);
|
|
247
|
+
}
|
|
238
248
|
logger.error("Failed to persist new rule", { err: error });
|
|
239
249
|
return NextResponse.json(
|
|
240
250
|
{ error: t("business_rules.errors.createFailed") },
|
|
@@ -404,7 +414,8 @@ const openApi = {
|
|
|
404
414
|
],
|
|
405
415
|
errors: [
|
|
406
416
|
{ status: 400, description: "Invalid payload", schema: errorResponseSchema },
|
|
407
|
-
{ status: 401, description: "Unauthorized", schema: errorResponseSchema }
|
|
417
|
+
{ status: 401, description: "Unauthorized", schema: errorResponseSchema },
|
|
418
|
+
{ status: 409, description: "A rule with this Rule ID already exists for the tenant", schema: errorResponseSchema }
|
|
408
419
|
]
|
|
409
420
|
},
|
|
410
421
|
PUT: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/business_rules/api/rules/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { BusinessRule } from '../../data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport {\n createBusinessRuleSchema,\n updateBusinessRuleSchema,\n createLocalizedBusinessRuleSchema,\n createLocalizedUpdateBusinessRuleSchema,\n businessRuleFilterSchema,\n ruleTypeSchema,\n} from '../../data/validators'\nimport {\n invalidateBusinessRuleDiscoveryCache,\n resolveBusinessRuleDiscoveryCache,\n} from '../../lib/rule-engine'\nimport { validateOpenMercatoCallActions } from '../../lib/openmercato-call-options'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('business_rules').child({ component: 'rules-api' })\n\nconst querySchema = z.looseObject({\n id: z.uuid().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n ruleId: z.string().optional(),\n ruleType: ruleTypeSchema.optional(),\n entityType: z.string().optional(),\n eventType: z.string().optional(),\n enabled: z.coerce.boolean().optional(),\n ruleCategory: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional().default('desc'),\n})\n\nconst ruleListItemSchema = z.object({\n id: z.string().uuid(),\n ruleId: z.string(),\n ruleName: z.string(),\n description: z.string().nullable(),\n ruleType: ruleTypeSchema,\n ruleCategory: z.string().nullable(),\n entityType: z.string(),\n eventType: z.string().nullable(),\n enabled: z.boolean(),\n priority: z.number(),\n version: z.number(),\n effectiveFrom: z.string().nullable(),\n effectiveTo: z.string().nullable(),\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst ruleListResponseSchema = z.object({\n items: z.array(ruleListItemSchema),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n})\n\nconst ruleCreateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst okResponseSchema = z.object({\n ok: z.literal(true),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n})\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['business_rules.view'] },\n POST: { requireAuth: true, requireFeatures: ['business_rules.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['business_rules.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['business_rules.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nfunction hasOpenMercatoCallAction(actions: unknown): boolean {\n return Array.isArray(actions)\n && actions.some((action) => action && typeof action === 'object' && (action as { type?: unknown }).type === 'CALL_OPEN_MERCATO')\n}\n\nasync function requireOpenMercatoCallConfiguratorAccess(\n container: ReturnType<typeof createRequestContainer> extends Promise<infer T> ? T : never,\n auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>,\n): Promise<Response | null> {\n if (!auth.sub) {\n return NextResponse.json(\n { error: 'Forbidden', requiredFeatures: ['api_keys.create'] },\n { status: 403 },\n )\n }\n\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n required: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n } | undefined\n const allowed = await rbac?.userHasAllFeatures?.(auth.sub, ['api_keys.create'], {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n })\n if (allowed) return null\n\n return NextResponse.json(\n { error: 'Forbidden', requiredFeatures: ['api_keys.create'] },\n { status: 403 },\n )\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = querySchema.safeParse({\n id: url.searchParams.get('id') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n search: url.searchParams.get('search') || undefined,\n ruleId: url.searchParams.get('ruleId') || undefined,\n ruleType: url.searchParams.get('ruleType') || undefined,\n entityType: url.searchParams.get('entityType') || undefined,\n eventType: url.searchParams.get('eventType') || undefined,\n enabled: url.searchParams.get('enabled') || undefined,\n ruleCategory: url.searchParams.get('ruleCategory') || undefined,\n sortField: url.searchParams.get('sortField') || undefined,\n sortDir: url.searchParams.get('sortDir') || undefined,\n })\n\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query parameters' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const { id, page, pageSize, search, ruleId, ruleType, entityType, eventType, enabled, ruleCategory, sortField, sortDir } = parsed.data\n\n const filters: Record<string, any> = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n deletedAt: null,\n }\n\n if (id) filters.id = id\n if (ruleId) filters.ruleId = { $ilike: `%${escapeLikePattern(ruleId)}%` }\n if (search) filters.ruleName = { $ilike: `%${escapeLikePattern(search)}%` }\n if (ruleType) filters.ruleType = ruleType\n if (entityType) filters.entityType = entityType\n if (eventType) filters.eventType = eventType\n if (enabled !== undefined) filters.enabled = enabled\n if (ruleCategory) filters.ruleCategory = ruleCategory\n\n const sortFieldMap: Record<string, string> = {\n ruleId: 'ruleId',\n ruleName: 'ruleName',\n ruleType: 'ruleType',\n entityType: 'entityType',\n priority: 'priority',\n createdAt: 'createdAt',\n updatedAt: 'updatedAt',\n }\n\n const orderByField = sortField && sortFieldMap[sortField] ? sortFieldMap[sortField] : 'priority'\n const orderBy = { [orderByField]: sortDir, ruleId: 'asc' as const }\n\n const [rows, count] = await em.findAndCount(\n BusinessRule,\n filters,\n {\n limit: pageSize,\n offset: (page - 1) * pageSize,\n orderBy,\n }\n )\n\n const items = rows.map((rule) => ({\n id: rule.id,\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n description: rule.description ?? null,\n ruleType: rule.ruleType,\n ruleCategory: rule.ruleCategory ?? null,\n entityType: rule.entityType,\n eventType: rule.eventType ?? null,\n enabled: rule.enabled,\n priority: rule.priority,\n version: rule.version,\n effectiveFrom: rule.effectiveFrom ? rule.effectiveFrom.toISOString() : null,\n effectiveTo: rule.effectiveTo ? rule.effectiveTo.toISOString() : null,\n tenantId: rule.tenantId,\n organizationId: rule.organizationId,\n createdAt: rule.createdAt.toISOString(),\n updatedAt: rule.updatedAt.toISOString(),\n }))\n\n const totalPages = Math.max(1, Math.ceil(count / pageSize))\n\n return NextResponse.json({ items, total: count, totalPages })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n if (!auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Tenant and organization context required' }, { status: 400 })\n }\n const tenantId = auth.tenantId\n const organizationId = auth.orgId\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const cache = resolveBusinessRuleDiscoveryCache(container.resolve.bind(container))\n\n let body: any\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n\n const payload = {\n ...body,\n tenantId,\n organizationId,\n createdBy: auth.sub ?? auth.email ?? null,\n }\n\n const { t } = await resolveTranslations()\n const schema = createLocalizedBusinessRuleSchema(t)\n const parsed = schema.safeParse(payload)\n if (!parsed.success) {\n const errors = parsed.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)\n return NextResponse.json({ error: `Validation failed: ${errors.join(', ')}` }, { status: 400 })\n }\n\n if (hasOpenMercatoCallAction(parsed.data.successActions) || hasOpenMercatoCallAction(parsed.data.failureActions)) {\n const accessError = await requireOpenMercatoCallConfiguratorAccess(container, auth)\n if (accessError) return accessError\n }\n\n const actionErrors = [\n ...await validateOpenMercatoCallActions(em as EntityManager, parsed.data.successActions, {\n tenantId,\n organizationId,\n }),\n ...await validateOpenMercatoCallActions(em as EntityManager, parsed.data.failureActions, {\n tenantId,\n organizationId,\n }),\n ]\n if (actionErrors.length > 0) {\n return NextResponse.json({ error: `Validation failed: ${actionErrors.join(', ')}` }, { status: 400 })\n }\n\n const data = {\n ...parsed.data,\n conditionExpression: parsed.data.conditionExpression ?? null,\n }\n\n const rule = em.create(BusinessRule, data)\n\n try {\n await em.persist(rule).flush()\n await invalidateBusinessRuleDiscoveryCache(cache, rule.tenantId, rule.organizationId)\n } catch (error) {\n logger.error('Failed to persist new rule', { err: error })\n return NextResponse.json(\n { error: t('business_rules.errors.createFailed') },\n { status: 500 },\n )\n }\n\n return NextResponse.json({ id: rule.id }, { status: 201 })\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n if (!auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Tenant and organization context required' }, { status: 400 })\n }\n const tenantId = auth.tenantId\n const organizationId = auth.orgId\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const cache = resolveBusinessRuleDiscoveryCache(container.resolve.bind(container))\n\n let body: any\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n\n if (!body.id) {\n return NextResponse.json({ error: 'Rule id is required' }, { status: 400 })\n }\n\n const payload = {\n ...body,\n updatedBy: auth.sub ?? auth.email ?? null,\n }\n delete (payload as Record<string, unknown>).tenantId\n delete (payload as Record<string, unknown>).organizationId\n delete (payload as Record<string, unknown>).createdBy\n\n const { t } = await resolveTranslations()\n const schema = createLocalizedUpdateBusinessRuleSchema(t)\n const parsed = schema.safeParse(payload)\n if (!parsed.success) {\n const errors = parsed.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)\n return NextResponse.json({ error: `Validation failed: ${errors.join(', ')}` }, { status: 400 })\n }\n\n const rule = await em.findOne(BusinessRule, {\n id: parsed.data.id,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (!rule) {\n return NextResponse.json({ error: 'Rule not found' }, { status: 404 })\n }\n\n const touchesOpenMercatoCall = hasOpenMercatoCallAction(parsed.data.successActions)\n || hasOpenMercatoCallAction(parsed.data.failureActions)\n || hasOpenMercatoCallAction(rule.successActions)\n || hasOpenMercatoCallAction(rule.failureActions)\n if (touchesOpenMercatoCall) {\n const accessError = await requireOpenMercatoCallConfiguratorAccess(container, auth)\n if (accessError) return accessError\n }\n\n const actionErrors = [\n ...await validateOpenMercatoCallActions(em as EntityManager, parsed.data.successActions, {\n tenantId,\n organizationId,\n }),\n ...await validateOpenMercatoCallActions(em as EntityManager, parsed.data.failureActions, {\n tenantId,\n organizationId,\n }),\n ]\n if (actionErrors.length > 0) {\n return NextResponse.json({ error: `Validation failed: ${actionErrors.join(', ')}` }, { status: 400 })\n }\n\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'business_rules.rule',\n resourceId: rule.id,\n current: rule.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n em.assign(rule, parsed.data)\n\n try {\n await em.persist(rule).flush()\n await invalidateBusinessRuleDiscoveryCache(cache, rule.tenantId, rule.organizationId)\n } catch (error) {\n logger.error('Failed to persist rule update', { err: error })\n return NextResponse.json(\n { error: t('business_rules.errors.updateFailed') },\n { status: 500 },\n )\n }\n\n return NextResponse.json({ ok: true })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const id = url.searchParams.get('id')\n\n if (!id) {\n return NextResponse.json({ error: 'Rule id is required' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const cache = resolveBusinessRuleDiscoveryCache(container.resolve.bind(container))\n\n const rule = await em.findOne(BusinessRule, {\n id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n deletedAt: null,\n })\n\n if (!rule) {\n return NextResponse.json({ error: 'Rule not found' }, { status: 404 })\n }\n\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'business_rules.rule',\n resourceId: rule.id,\n current: rule.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n rule.deletedAt = new Date()\n await em.persist(rule).flush()\n await invalidateBusinessRuleDiscoveryCache(cache, rule.tenantId, rule.organizationId)\n\n return NextResponse.json({ ok: true })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Business Rules',\n summary: 'Business rule management',\n methods: {\n GET: {\n summary: 'List business rules',\n description: 'Returns business rules for the current tenant and organization with filtering and pagination.',\n query: querySchema,\n responses: [\n { status: 200, description: 'Business rules collection', schema: ruleListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n POST: {\n summary: 'Create business rule',\n description: 'Creates a new business rule for the current tenant and organization.',\n requestBody: {\n contentType: 'application/json',\n schema: createBusinessRuleSchema,\n },\n responses: [\n {\n status: 201,\n description: 'Business rule created',\n schema: ruleCreateResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n PUT: {\n summary: 'Update business rule',\n description: 'Updates an existing business rule.',\n requestBody: {\n contentType: 'application/json',\n schema: updateBusinessRuleSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Business rule updated',\n schema: okResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'Business rule not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Delete business rule',\n description: 'Soft deletes a business rule by identifier.',\n query: z.object({ id: z.string().uuid().describe('Business rule identifier') }),\n responses: [\n { status: 200, description: 'Business rule deleted', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid identifier', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'Business rule not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { isUniqueViolation } from '@open-mercato/shared/lib/db/pg-errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { BusinessRule } from '../../data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport {\n createBusinessRuleSchema,\n updateBusinessRuleSchema,\n createLocalizedBusinessRuleSchema,\n createLocalizedUpdateBusinessRuleSchema,\n businessRuleFilterSchema,\n ruleTypeSchema,\n} from '../../data/validators'\nimport {\n invalidateBusinessRuleDiscoveryCache,\n resolveBusinessRuleDiscoveryCache,\n} from '../../lib/rule-engine'\nimport { validateOpenMercatoCallActions } from '../../lib/openmercato-call-options'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('business_rules').child({ component: 'rules-api' })\n\nconst querySchema = z.looseObject({\n id: z.uuid().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n ruleId: z.string().optional(),\n ruleType: ruleTypeSchema.optional(),\n entityType: z.string().optional(),\n eventType: z.string().optional(),\n enabled: z.coerce.boolean().optional(),\n ruleCategory: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional().default('desc'),\n})\n\nconst ruleListItemSchema = z.object({\n id: z.string().uuid(),\n ruleId: z.string(),\n ruleName: z.string(),\n description: z.string().nullable(),\n ruleType: ruleTypeSchema,\n ruleCategory: z.string().nullable(),\n entityType: z.string(),\n eventType: z.string().nullable(),\n enabled: z.boolean(),\n priority: z.number(),\n version: z.number(),\n effectiveFrom: z.string().nullable(),\n effectiveTo: z.string().nullable(),\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\n\nconst ruleListResponseSchema = z.object({\n items: z.array(ruleListItemSchema),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n})\n\nconst ruleCreateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst okResponseSchema = z.object({\n ok: z.literal(true),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n})\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['business_rules.view'] },\n POST: { requireAuth: true, requireFeatures: ['business_rules.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['business_rules.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['business_rules.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nfunction hasOpenMercatoCallAction(actions: unknown): boolean {\n return Array.isArray(actions)\n && actions.some((action) => action && typeof action === 'object' && (action as { type?: unknown }).type === 'CALL_OPEN_MERCATO')\n}\n\nasync function requireOpenMercatoCallConfiguratorAccess(\n container: ReturnType<typeof createRequestContainer> extends Promise<infer T> ? T : never,\n auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>,\n): Promise<Response | null> {\n if (!auth.sub) {\n return NextResponse.json(\n { error: 'Forbidden', requiredFeatures: ['api_keys.create'] },\n { status: 403 },\n )\n }\n\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n required: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n } | undefined\n const allowed = await rbac?.userHasAllFeatures?.(auth.sub, ['api_keys.create'], {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n })\n if (allowed) return null\n\n return NextResponse.json(\n { error: 'Forbidden', requiredFeatures: ['api_keys.create'] },\n { status: 403 },\n )\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = querySchema.safeParse({\n id: url.searchParams.get('id') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n search: url.searchParams.get('search') || undefined,\n ruleId: url.searchParams.get('ruleId') || undefined,\n ruleType: url.searchParams.get('ruleType') || undefined,\n entityType: url.searchParams.get('entityType') || undefined,\n eventType: url.searchParams.get('eventType') || undefined,\n enabled: url.searchParams.get('enabled') || undefined,\n ruleCategory: url.searchParams.get('ruleCategory') || undefined,\n sortField: url.searchParams.get('sortField') || undefined,\n sortDir: url.searchParams.get('sortDir') || undefined,\n })\n\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query parameters' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const { id, page, pageSize, search, ruleId, ruleType, entityType, eventType, enabled, ruleCategory, sortField, sortDir } = parsed.data\n\n const filters: Record<string, any> = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n deletedAt: null,\n }\n\n if (id) filters.id = id\n if (ruleId) filters.ruleId = { $ilike: `%${escapeLikePattern(ruleId)}%` }\n if (search) filters.ruleName = { $ilike: `%${escapeLikePattern(search)}%` }\n if (ruleType) filters.ruleType = ruleType\n if (entityType) filters.entityType = entityType\n if (eventType) filters.eventType = eventType\n if (enabled !== undefined) filters.enabled = enabled\n if (ruleCategory) filters.ruleCategory = ruleCategory\n\n const sortFieldMap: Record<string, string> = {\n ruleId: 'ruleId',\n ruleName: 'ruleName',\n ruleType: 'ruleType',\n entityType: 'entityType',\n priority: 'priority',\n createdAt: 'createdAt',\n updatedAt: 'updatedAt',\n }\n\n const orderByField = sortField && sortFieldMap[sortField] ? sortFieldMap[sortField] : 'priority'\n const orderBy = { [orderByField]: sortDir, ruleId: 'asc' as const }\n\n const [rows, count] = await em.findAndCount(\n BusinessRule,\n filters,\n {\n limit: pageSize,\n offset: (page - 1) * pageSize,\n orderBy,\n }\n )\n\n const items = rows.map((rule) => ({\n id: rule.id,\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n description: rule.description ?? null,\n ruleType: rule.ruleType,\n ruleCategory: rule.ruleCategory ?? null,\n entityType: rule.entityType,\n eventType: rule.eventType ?? null,\n enabled: rule.enabled,\n priority: rule.priority,\n version: rule.version,\n effectiveFrom: rule.effectiveFrom ? rule.effectiveFrom.toISOString() : null,\n effectiveTo: rule.effectiveTo ? rule.effectiveTo.toISOString() : null,\n tenantId: rule.tenantId,\n organizationId: rule.organizationId,\n createdAt: rule.createdAt.toISOString(),\n updatedAt: rule.updatedAt.toISOString(),\n }))\n\n const totalPages = Math.max(1, Math.ceil(count / pageSize))\n\n return NextResponse.json({ items, total: count, totalPages })\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n if (!auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Tenant and organization context required' }, { status: 400 })\n }\n const tenantId = auth.tenantId\n const organizationId = auth.orgId\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const cache = resolveBusinessRuleDiscoveryCache(container.resolve.bind(container))\n\n let body: any\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n\n const payload = {\n ...body,\n tenantId,\n organizationId,\n createdBy: auth.sub ?? auth.email ?? null,\n }\n\n const { t } = await resolveTranslations()\n const schema = createLocalizedBusinessRuleSchema(t)\n const parsed = schema.safeParse(payload)\n if (!parsed.success) {\n const errors = parsed.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)\n return NextResponse.json({ error: `Validation failed: ${errors.join(', ')}` }, { status: 400 })\n }\n\n if (hasOpenMercatoCallAction(parsed.data.successActions) || hasOpenMercatoCallAction(parsed.data.failureActions)) {\n const accessError = await requireOpenMercatoCallConfiguratorAccess(container, auth)\n if (accessError) return accessError\n }\n\n const actionErrors = [\n ...await validateOpenMercatoCallActions(em as EntityManager, parsed.data.successActions, {\n tenantId,\n organizationId,\n }),\n ...await validateOpenMercatoCallActions(em as EntityManager, parsed.data.failureActions, {\n tenantId,\n organizationId,\n }),\n ]\n if (actionErrors.length > 0) {\n return NextResponse.json({ error: `Validation failed: ${actionErrors.join(', ')}` }, { status: 400 })\n }\n\n const data = {\n ...parsed.data,\n conditionExpression: parsed.data.conditionExpression ?? null,\n }\n\n const rule = em.create(BusinessRule, data)\n\n try {\n await em.persist(rule).flush()\n await invalidateBusinessRuleDiscoveryCache(cache, rule.tenantId, rule.organizationId)\n } catch (error) {\n if (isUniqueViolation(error)) {\n return NextResponse.json(\n {\n error: t('business_rules.errors.duplicateRuleId', { ruleId: parsed.data.ruleId }),\n fieldErrors: { ruleId: t('business_rules.errors.duplicateRuleId', { ruleId: parsed.data.ruleId }) },\n },\n { status: 409 },\n )\n }\n logger.error('Failed to persist new rule', { err: error })\n return NextResponse.json(\n { error: t('business_rules.errors.createFailed') },\n { status: 500 },\n )\n }\n\n return NextResponse.json({ id: rule.id }, { status: 201 })\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n if (!auth.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Tenant and organization context required' }, { status: 400 })\n }\n const tenantId = auth.tenantId\n const organizationId = auth.orgId\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const cache = resolveBusinessRuleDiscoveryCache(container.resolve.bind(container))\n\n let body: any\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n\n if (!body.id) {\n return NextResponse.json({ error: 'Rule id is required' }, { status: 400 })\n }\n\n const payload = {\n ...body,\n updatedBy: auth.sub ?? auth.email ?? null,\n }\n delete (payload as Record<string, unknown>).tenantId\n delete (payload as Record<string, unknown>).organizationId\n delete (payload as Record<string, unknown>).createdBy\n\n const { t } = await resolveTranslations()\n const schema = createLocalizedUpdateBusinessRuleSchema(t)\n const parsed = schema.safeParse(payload)\n if (!parsed.success) {\n const errors = parsed.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)\n return NextResponse.json({ error: `Validation failed: ${errors.join(', ')}` }, { status: 400 })\n }\n\n const rule = await em.findOne(BusinessRule, {\n id: parsed.data.id,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (!rule) {\n return NextResponse.json({ error: 'Rule not found' }, { status: 404 })\n }\n\n const touchesOpenMercatoCall = hasOpenMercatoCallAction(parsed.data.successActions)\n || hasOpenMercatoCallAction(parsed.data.failureActions)\n || hasOpenMercatoCallAction(rule.successActions)\n || hasOpenMercatoCallAction(rule.failureActions)\n if (touchesOpenMercatoCall) {\n const accessError = await requireOpenMercatoCallConfiguratorAccess(container, auth)\n if (accessError) return accessError\n }\n\n const actionErrors = [\n ...await validateOpenMercatoCallActions(em as EntityManager, parsed.data.successActions, {\n tenantId,\n organizationId,\n }),\n ...await validateOpenMercatoCallActions(em as EntityManager, parsed.data.failureActions, {\n tenantId,\n organizationId,\n }),\n ]\n if (actionErrors.length > 0) {\n return NextResponse.json({ error: `Validation failed: ${actionErrors.join(', ')}` }, { status: 400 })\n }\n\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'business_rules.rule',\n resourceId: rule.id,\n current: rule.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n em.assign(rule, parsed.data)\n\n try {\n await em.persist(rule).flush()\n await invalidateBusinessRuleDiscoveryCache(cache, rule.tenantId, rule.organizationId)\n } catch (error) {\n logger.error('Failed to persist rule update', { err: error })\n return NextResponse.json(\n { error: t('business_rules.errors.updateFailed') },\n { status: 500 },\n )\n }\n\n return NextResponse.json({ ok: true })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const id = url.searchParams.get('id')\n\n if (!id) {\n return NextResponse.json({ error: 'Rule id is required' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const cache = resolveBusinessRuleDiscoveryCache(container.resolve.bind(container))\n\n const rule = await em.findOne(BusinessRule, {\n id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n deletedAt: null,\n })\n\n if (!rule) {\n return NextResponse.json({ error: 'Rule not found' }, { status: 404 })\n }\n\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'business_rules.rule',\n resourceId: rule.id,\n current: rule.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n rule.deletedAt = new Date()\n await em.persist(rule).flush()\n await invalidateBusinessRuleDiscoveryCache(cache, rule.tenantId, rule.organizationId)\n\n return NextResponse.json({ ok: true })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Business Rules',\n summary: 'Business rule management',\n methods: {\n GET: {\n summary: 'List business rules',\n description: 'Returns business rules for the current tenant and organization with filtering and pagination.',\n query: querySchema,\n responses: [\n { status: 200, description: 'Business rules collection', schema: ruleListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n POST: {\n summary: 'Create business rule',\n description: 'Creates a new business rule for the current tenant and organization.',\n requestBody: {\n contentType: 'application/json',\n schema: createBusinessRuleSchema,\n },\n responses: [\n {\n status: 201,\n description: 'Business rule created',\n schema: ruleCreateResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 409, description: 'A rule with this Rule ID already exists for the tenant', schema: errorResponseSchema },\n ],\n },\n PUT: {\n summary: 'Update business rule',\n description: 'Updates an existing business rule.',\n requestBody: {\n contentType: 'application/json',\n schema: updateBusinessRuleSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Business rule updated',\n schema: okResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'Business rule not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Delete business rule',\n description: 'Soft deletes a business rule by identifier.',\n query: z.object({ id: z.string().uuid().describe('Business rule identifier') }),\n responses: [\n { status: 200, description: 'Business rule deleted', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid identifier', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'Business rule not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -99,7 +99,8 @@ import {
|
|
|
99
99
|
toTrimmedOrNull,
|
|
100
100
|
toPositiveNumberOrNull,
|
|
101
101
|
toIntegerInRangeOrDefault,
|
|
102
|
-
normalizeProductConversionInputs
|
|
102
|
+
normalizeProductConversionInputs,
|
|
103
|
+
pickEnrichmentNamespaces
|
|
103
104
|
} from "@open-mercato/core/modules/catalog/components/products/productFormUtils";
|
|
104
105
|
import {
|
|
105
106
|
AlignLeft,
|
|
@@ -515,7 +516,11 @@ function EditCatalogProductPage({
|
|
|
515
516
|
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : typeof record.updated_at === "string" ? record.updated_at : null
|
|
516
517
|
};
|
|
517
518
|
if (!cancelled) {
|
|
518
|
-
setInitialValues({
|
|
519
|
+
setInitialValues({
|
|
520
|
+
...initial,
|
|
521
|
+
...customValues,
|
|
522
|
+
...pickEnrichmentNamespaces(record)
|
|
523
|
+
});
|
|
519
524
|
setCategorizeOptions({
|
|
520
525
|
categories: categoryOptions,
|
|
521
526
|
channels: channelOptionEntries,
|
|
@@ -1057,10 +1062,20 @@ function EditCatalogProductPage({
|
|
|
1057
1062
|
}
|
|
1058
1063
|
const updateResult = await updateCrud("catalog/products", payload);
|
|
1059
1064
|
const refreshedUpdatedAt = typeof updateResult.result?.updatedAt === "string" ? updateResult.result.updatedAt : null;
|
|
1065
|
+
let refreshedEnrichments = {};
|
|
1066
|
+
if (Object.keys(pickEnrichmentNamespaces(formValues)).length > 0) {
|
|
1067
|
+
const refreshedProductRes = await apiCall(
|
|
1068
|
+
`/api/catalog/products?id=${encodeURIComponent(productId)}&page=1&pageSize=1&withDeleted=false`
|
|
1069
|
+
);
|
|
1070
|
+
refreshedEnrichments = pickEnrichmentNamespaces(
|
|
1071
|
+
refreshedProductRes.ok ? refreshedProductRes.result?.items?.[0] : void 0
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1060
1074
|
setInitialValues(
|
|
1061
1075
|
(prev) => prev ? {
|
|
1062
1076
|
...prev,
|
|
1063
1077
|
...values,
|
|
1078
|
+
...refreshedEnrichments,
|
|
1064
1079
|
updatedAt: refreshedUpdatedAt ?? prev.updatedAt
|
|
1065
1080
|
} : prev
|
|
1066
1081
|
);
|