@open-mercato/core 0.6.4-develop.4236.1.9fa6806b34 → 0.6.4-develop.4239.1.4a264a5828

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.
Files changed (39) hide show
  1. package/dist/modules/business_rules/backend/logs/[id]/page.js +24 -5
  2. package/dist/modules/business_rules/backend/logs/[id]/page.js.map +2 -2
  3. package/dist/modules/catalog/api/offers/route.js +15 -5
  4. package/dist/modules/catalog/api/offers/route.js.map +2 -2
  5. package/dist/modules/currencies/backend/currencies/[id]/page.js +19 -2
  6. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  7. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +27 -7
  8. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  9. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +27 -7
  10. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  11. package/dist/modules/customers/backend/customers/people/[id]/page.js +29 -8
  12. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  13. package/dist/modules/progress/acl.js +8 -4
  14. package/dist/modules/progress/acl.js.map +2 -2
  15. package/dist/modules/workflows/backend/events/[id]/page.js +24 -6
  16. package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
  17. package/dist/modules/workflows/backend/instances/[id]/page.js +27 -5
  18. package/dist/modules/workflows/backend/instances/[id]/page.js.map +2 -2
  19. package/dist/modules/workflows/backend/tasks/[id]/page.js +25 -6
  20. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  21. package/package.json +7 -7
  22. package/src/modules/business_rules/backend/logs/[id]/page.tsx +32 -7
  23. package/src/modules/catalog/api/offers/route.ts +20 -5
  24. package/src/modules/currencies/backend/currencies/[id]/page.tsx +21 -2
  25. package/src/modules/currencies/i18n/de.json +1 -0
  26. package/src/modules/currencies/i18n/en.json +1 -0
  27. package/src/modules/currencies/i18n/es.json +1 -0
  28. package/src/modules/currencies/i18n/pl.json +1 -0
  29. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -11
  30. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +34 -11
  31. package/src/modules/customers/backend/customers/people/[id]/page.tsx +35 -11
  32. package/src/modules/progress/acl.ts +4 -0
  33. package/src/modules/workflows/backend/events/[id]/page.tsx +32 -10
  34. package/src/modules/workflows/backend/instances/[id]/page.tsx +33 -9
  35. package/src/modules/workflows/backend/tasks/[id]/page.tsx +33 -10
  36. package/src/modules/workflows/i18n/de.json +1 -0
  37. package/src/modules/workflows/i18n/en.json +1 -0
  38. package/src/modules/workflows/i18n/es.json +1 -0
  39. package/src/modules/workflows/i18n/pl.json +1 -0
@@ -9,6 +9,7 @@ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
9
9
  import { Button } from "@open-mercato/ui/primitives/button";
10
10
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
11
11
  import { JsonDisplay } from "@open-mercato/ui/backend/JsonDisplay";
12
+ import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
12
13
  function ExecutionLogDetailPage() {
13
14
  const router = useRouter();
14
15
  const params = useParams();
@@ -24,7 +25,11 @@ function ExecutionLogDetailPage() {
24
25
  queryFn: async () => {
25
26
  const response = await apiFetch(`/api/business_rules/logs/${logId}`);
26
27
  if (!response.ok) {
27
- throw new Error(t("business_rules.logs.errors.fetchFailed"));
28
+ const httpErr = new Error(
29
+ response.status === 404 ? t("business_rules.logs.errors.notFound", "Execution log not found.") : t("business_rules.logs.errors.fetchFailed")
30
+ );
31
+ httpErr.status = response.status;
32
+ throw httpErr;
28
33
  }
29
34
  const result = await response.json();
30
35
  return result;
@@ -37,11 +42,25 @@ function ExecutionLogDetailPage() {
37
42
  /* @__PURE__ */ jsx("span", { children: t("business_rules.logs.detail.loading") })
38
43
  ] }) }) });
39
44
  }
45
+ const isNotFound = !isLoading && error?.status === 404;
46
+ if (isNotFound) {
47
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
48
+ RecordNotFoundState,
49
+ {
50
+ label: t("business_rules.logs.errors.notFound", "Execution log not found."),
51
+ backHref: "/backend/logs",
52
+ backLabel: t("business_rules.logs.backToList", "Back to logs")
53
+ }
54
+ ) }) });
55
+ }
40
56
  if (error || !log) {
41
- return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsxs("div", { className: "flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground", children: [
42
- /* @__PURE__ */ jsx("p", { children: error ? t("business_rules.logs.errors.loadFailed") : t("business_rules.logs.errors.notFound") }),
43
- /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", children: /* @__PURE__ */ jsx(Link, { href: "/backend/logs", children: t("business_rules.logs.backToList") }) })
44
- ] }) }) });
57
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
58
+ ErrorMessage,
59
+ {
60
+ label: error?.message ?? t("business_rules.logs.errors.loadFailed"),
61
+ action: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/logs", children: t("business_rules.logs.backToList", "Back to logs") }) })
62
+ }
63
+ ) }) });
45
64
  }
46
65
  const getResultBadgeClass = (result) => {
47
66
  switch (result) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/business_rules/backend/logs/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useParams, useRouter } from 'next/navigation'\nimport { useQuery } from '@tanstack/react-query'\nimport { apiFetch } from '@open-mercato/ui/backend/utils/api'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { JsonDisplay } from '@open-mercato/ui/backend/JsonDisplay'\n\ntype RuleExecutionLog = {\n id: string\n ruleId: string\n rule?: {\n id: string\n ruleId: string\n ruleName: string\n ruleType: string\n } | null\n entityType: string\n entityId: string | null\n eventType: string | null\n executedAt: string\n executionTimeMs: number\n executionResult: 'SUCCESS' | 'FAILURE' | 'ERROR'\n resultValue: any | null\n errorMessage: string | null\n inputContext: any | null\n outputContext: any | null\n executedBy: string | null\n tenantId: string | null\n organizationId: string | null\n}\n\nexport default function ExecutionLogDetailPage() {\n const router = useRouter()\n const params = useParams()\n\n // Handle catch-all route: params.slug = ['logs', 'id']\n let logId: string | undefined\n if (params?.slug && Array.isArray(params.slug)) {\n logId = params.slug[1] // Second element is the ID\n } else if (params?.id) {\n logId = Array.isArray(params.id) ? params.id[0] : params.id\n }\n\n const t = useT()\n\n const { data: log, isLoading, error } = useQuery({\n queryKey: ['business-rules', 'logs', logId],\n queryFn: async () => {\n const response = await apiFetch(`/api/business_rules/logs/${logId}`)\n if (!response.ok) {\n throw new Error(t('business_rules.logs.errors.fetchFailed'))\n }\n const result = await response.json()\n return result as RuleExecutionLog\n },\n enabled: !!logId,\n })\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('business_rules.logs.detail.loading')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (error || !log) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <p>{error ? t('business_rules.logs.errors.loadFailed') : t('business_rules.logs.errors.notFound')}</p>\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/logs\">{t('business_rules.logs.backToList')}</Link>\n </Button>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n const getResultBadgeClass = (result: string) => {\n switch (result) {\n case 'SUCCESS':\n return 'bg-green-100 text-green-800'\n case 'FAILURE':\n return 'bg-yellow-100 text-yellow-800'\n case 'ERROR':\n return 'bg-red-100 text-red-800'\n default:\n return 'bg-muted text-foreground'\n }\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {t('business_rules.logs.detail.title')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t('business_rules.logs.detail.logId')}: {log.id}\n </p>\n </div>\n <Button onClick={() => router.push('/backend/logs')} variant=\"outline\">\n {t('business_rules.logs.backToList')}\n </Button>\n </div>\n\n {/* Execution Summary */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.summary')}\n </h2>\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executedAt')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {new Date(log.executedAt).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.result')}\n </dt>\n <dd className=\"mt-1\">\n <span\n className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium ${getResultBadgeClass(\n log.executionResult\n )}`}\n >\n {t(`business_rules.logs.result.${log.executionResult.toLowerCase()}`)}\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executionTime')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.executionTimeMs}ms</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executedBy')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {log.executedBy || t('common.unknown')}\n </dd>\n </div>\n </dl>\n </div>\n\n {/* Rule Information */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.ruleInfo')}\n </h2>\n {log.rule ? (\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleName')}\n </dt>\n <dd className=\"mt-1\">\n <Link\n href={`/backend/rules/${log.rule.id}`}\n className=\"text-sm text-primary hover:underline\"\n >\n {log.rule.ruleName}\n </Link>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.rule.ruleType}</dd>\n </div>\n <div className=\"md:col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">{log.rule.ruleId}</dd>\n </div>\n </dl>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('business_rules.logs.ruleDeleted')}</p>\n )}\n </div>\n\n {/* Entity Information */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.entityInfo')}\n </h2>\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.entityType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.entityType}</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.eventType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {log.eventType || t('common.none')}\n </dd>\n </div>\n {log.entityId && (\n <div className=\"md:col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.entityId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono break-all\">\n {log.entityId}\n </dd>\n </div>\n )}\n </dl>\n </div>\n\n {/* Error Message (if present) */}\n {log.errorMessage && (\n <div className=\"rounded-lg border border-destructive bg-destructive/5 p-6\">\n <h2 className=\"text-lg font-semibold mb-4 text-destructive\">\n {t('business_rules.logs.detail.errorMessage')}\n </h2>\n <pre className=\"text-sm text-destructive whitespace-pre-wrap font-mono\">\n {log.errorMessage}\n </pre>\n </div>\n )}\n\n {/* Input Context */}\n {log.inputContext && (\n <JsonDisplay\n data={log.inputContext}\n title={t('business_rules.logs.detail.inputContext')}\n />\n )}\n\n {/* Output Context */}\n {log.outputContext && (\n <JsonDisplay\n data={log.outputContext}\n title={t('business_rules.logs.detail.outputContext')}\n />\n )}\n\n {/* Result Value */}\n {log.resultValue && (\n <JsonDisplay\n data={log.resultValue}\n title={t('business_rules.logs.detail.resultValue')}\n />\n )}\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useParams, useRouter } from 'next/navigation'\nimport { useQuery } from '@tanstack/react-query'\nimport { apiFetch } from '@open-mercato/ui/backend/utils/api'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { JsonDisplay } from '@open-mercato/ui/backend/JsonDisplay'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype RuleExecutionLog = {\n id: string\n ruleId: string\n rule?: {\n id: string\n ruleId: string\n ruleName: string\n ruleType: string\n } | null\n entityType: string\n entityId: string | null\n eventType: string | null\n executedAt: string\n executionTimeMs: number\n executionResult: 'SUCCESS' | 'FAILURE' | 'ERROR'\n resultValue: any | null\n errorMessage: string | null\n inputContext: any | null\n outputContext: any | null\n executedBy: string | null\n tenantId: string | null\n organizationId: string | null\n}\n\nexport default function ExecutionLogDetailPage() {\n const router = useRouter()\n const params = useParams()\n\n // Handle catch-all route: params.slug = ['logs', 'id']\n let logId: string | undefined\n if (params?.slug && Array.isArray(params.slug)) {\n logId = params.slug[1] // Second element is the ID\n } else if (params?.id) {\n logId = Array.isArray(params.id) ? params.id[0] : params.id\n }\n\n const t = useT()\n\n const { data: log, isLoading, error } = useQuery({\n queryKey: ['business-rules', 'logs', logId],\n queryFn: async () => {\n const response = await apiFetch(`/api/business_rules/logs/${logId}`)\n if (!response.ok) {\n const httpErr = new Error(\n response.status === 404\n ? t('business_rules.logs.errors.notFound', 'Execution log not found.')\n : t('business_rules.logs.errors.fetchFailed')\n ) as Error & { status: number }\n httpErr.status = response.status\n throw httpErr\n }\n const result = await response.json()\n return result as RuleExecutionLog\n },\n enabled: !!logId,\n })\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('business_rules.logs.detail.loading')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n const isNotFound = !isLoading && (error as (Error & { status?: number }) | null)?.status === 404\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('business_rules.logs.errors.notFound', 'Execution log not found.')}\n backHref=\"/backend/logs\"\n backLabel={t('business_rules.logs.backToList', 'Back to logs')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !log) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={(error as Error | null)?.message ?? t('business_rules.logs.errors.loadFailed')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/logs\">{t('business_rules.logs.backToList', 'Back to logs')}</Link>\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n const getResultBadgeClass = (result: string) => {\n switch (result) {\n case 'SUCCESS':\n return 'bg-green-100 text-green-800'\n case 'FAILURE':\n return 'bg-yellow-100 text-yellow-800'\n case 'ERROR':\n return 'bg-red-100 text-red-800'\n default:\n return 'bg-muted text-foreground'\n }\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {t('business_rules.logs.detail.title')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {t('business_rules.logs.detail.logId')}: {log.id}\n </p>\n </div>\n <Button onClick={() => router.push('/backend/logs')} variant=\"outline\">\n {t('business_rules.logs.backToList')}\n </Button>\n </div>\n\n {/* Execution Summary */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.summary')}\n </h2>\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executedAt')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {new Date(log.executedAt).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.result')}\n </dt>\n <dd className=\"mt-1\">\n <span\n className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium ${getResultBadgeClass(\n log.executionResult\n )}`}\n >\n {t(`business_rules.logs.result.${log.executionResult.toLowerCase()}`)}\n </span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executionTime')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.executionTimeMs}ms</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.executedBy')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {log.executedBy || t('common.unknown')}\n </dd>\n </div>\n </dl>\n </div>\n\n {/* Rule Information */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.ruleInfo')}\n </h2>\n {log.rule ? (\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleName')}\n </dt>\n <dd className=\"mt-1\">\n <Link\n href={`/backend/rules/${log.rule.id}`}\n className=\"text-sm text-primary hover:underline\"\n >\n {log.rule.ruleName}\n </Link>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.rule.ruleType}</dd>\n </div>\n <div className=\"md:col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.ruleId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono\">{log.rule.ruleId}</dd>\n </div>\n </dl>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('business_rules.logs.ruleDeleted')}</p>\n )}\n </div>\n\n {/* Entity Information */}\n <div className=\"rounded-lg border bg-card p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {t('business_rules.logs.detail.entityInfo')}\n </h2>\n <dl className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.entityType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">{log.entityType}</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.eventType')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground\">\n {log.eventType || t('common.none')}\n </dd>\n </div>\n {log.entityId && (\n <div className=\"md:col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">\n {t('business_rules.logs.fields.entityId')}\n </dt>\n <dd className=\"mt-1 text-sm text-foreground font-mono break-all\">\n {log.entityId}\n </dd>\n </div>\n )}\n </dl>\n </div>\n\n {/* Error Message (if present) */}\n {log.errorMessage && (\n <div className=\"rounded-lg border border-destructive bg-destructive/5 p-6\">\n <h2 className=\"text-lg font-semibold mb-4 text-destructive\">\n {t('business_rules.logs.detail.errorMessage')}\n </h2>\n <pre className=\"text-sm text-destructive whitespace-pre-wrap font-mono\">\n {log.errorMessage}\n </pre>\n </div>\n )}\n\n {/* Input Context */}\n {log.inputContext && (\n <JsonDisplay\n data={log.inputContext}\n title={t('business_rules.logs.detail.inputContext')}\n />\n )}\n\n {/* Output Context */}\n {log.outputContext && (\n <JsonDisplay\n data={log.outputContext}\n title={t('business_rules.logs.detail.outputContext')}\n />\n )}\n\n {/* Result Value */}\n {log.resultValue && (\n <JsonDisplay\n data={log.resultValue}\n title={t('business_rules.logs.detail.resultValue')}\n />\n )}\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -75,10 +75,19 @@ async function decorateOffersWithDetails(items, ctx) {
75
75
  const productIds = items.map((item) => item?.productId ? String(item.productId) : null).filter((value) => !!value);
76
76
  if (!offerIds.length && !productIds.length) return;
77
77
  const em = ctx.container.resolve("em");
78
+ const scopeTenantId = ctx.auth?.tenantId ?? null;
79
+ if (!scopeTenantId) {
80
+ throw new CrudHttpError(403, "[internal] Missing tenant scope for offer decoration");
81
+ }
82
+ const scopeOrgIds = Array.isArray(ctx.organizationIds) && ctx.organizationIds.length ? Array.from(new Set(ctx.organizationIds)) : ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null ? [ctx.selectedOrganizationId ?? ctx.auth?.orgId] : [];
83
+ const scopeWhere = { tenantId: scopeTenantId };
84
+ if (scopeOrgIds.length === 1) scopeWhere.organizationId = scopeOrgIds[0];
85
+ else if (scopeOrgIds.length > 1) scopeWhere.organizationId = { $in: scopeOrgIds };
86
+ const scope = { tenantId: scopeTenantId, organizationId: scopeOrgIds.length === 1 ? scopeOrgIds[0] : null };
78
87
  const [products, prices, defaultVariants] = await Promise.all([
79
88
  productIds.length ? em.find(
80
89
  CatalogProduct,
81
- { id: { $in: productIds } },
90
+ { id: { $in: productIds }, ...scopeWhere },
82
91
  {
83
92
  fields: ["id", "title", "description", "defaultMediaId", "defaultMediaUrl", "sku"]
84
93
  }
@@ -86,13 +95,13 @@ async function decorateOffersWithDetails(items, ctx) {
86
95
  offerIds.length ? findWithDecryption(
87
96
  em,
88
97
  CatalogProductPrice,
89
- { offer: { $in: offerIds } },
98
+ { offer: { $in: offerIds }, ...scopeWhere },
90
99
  { populate: ["priceKind"] },
91
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null }
100
+ scope
92
101
  ) : [],
93
102
  productIds.length ? em.find(
94
103
  CatalogProductVariant,
95
- { product: { $in: productIds }, isDefault: true },
104
+ { product: { $in: productIds }, isDefault: true, ...scopeWhere },
96
105
  { fields: ["id", "product"] }
97
106
  ) : []
98
107
  ]);
@@ -174,6 +183,7 @@ async function decorateOffersWithDetails(items, ctx) {
174
183
  CatalogProductPrice,
175
184
  {
176
185
  offer: null,
186
+ ...scopeWhere,
177
187
  $and: [
178
188
  { $or: fallbackTargets },
179
189
  channelFilterValues.includes(null) ? {
@@ -185,7 +195,7 @@ async function decorateOffersWithDetails(items, ctx) {
185
195
  ]
186
196
  },
187
197
  { populate: ["priceKind"] },
188
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null }
198
+ scope
189
199
  ) : [];
190
200
  fallbackEntries.forEach((entry) => {
191
201
  const entryChannelId = typeof entry.channelId === "string" && entry.channelId.length ? entry.channelId : null;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/catalog/api/offers/route.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { CatalogOffer, CatalogProduct, CatalogProductPrice, CatalogProductVariant } from '../../data/entities'\nimport { offerCreateSchema, offerUpdateSchema } from '../../data/validators'\nimport { parseScopedCommandInput, resolveCrudRecordId } from '../utils'\nimport { E } from '#generated/entities.ids.generated'\nimport * as F from '#generated/entities/catalog_offer'\nimport { parseIdList } from '../products/route'\nimport { extractAllCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport {\n createCatalogCrudOpenApi,\n createPagedListResponseSchema,\n defaultOkResponseSchema,\n} from '../openapi'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n productId: z.string().uuid().optional(),\n channelId: z.string().uuid().optional(),\n channelIds: z.string().optional(),\n id: z.string().uuid().optional(),\n search: z.string().optional(),\n isActive: z.string().optional(),\n withDeleted: z.coerce.boolean().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\ntype OfferListQuery = z.infer<typeof listSchema>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['sales.channels.manage'] },\n POST: { requireAuth: true, requireFeatures: ['sales.channels.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['sales.channels.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['sales.channels.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nexport function normalizeSearch(term?: string | null): string | null {\n if (!term) return null\n const trimmed = term.trim()\n if (!trimmed.length) return null\n return trimmed\n}\n\nexport function buildOfferFilters(query: OfferListQuery): Record<string, unknown> {\n const filters: Record<string, unknown> = {}\n const searchTerm = normalizeSearch(query.search)\n if (query.id) {\n filters.id = { $eq: query.id }\n }\n if (query.productId) {\n filters.product_id = { $eq: query.productId }\n }\n if (query.channelId) {\n filters.channel_id = { $eq: query.channelId }\n } else {\n const channelIds = parseIdList(query.channelIds)\n if (channelIds.length) {\n filters.channel_id = { $in: channelIds }\n }\n }\n if (searchTerm) {\n const like = `%${escapeLikePattern(searchTerm)}%`\n filters.$or = [{ [F.title]: { $ilike: like } }, { [F.description]: { $ilike: like } }]\n }\n const isActive = parseBooleanToken(query.isActive)\n if (isActive !== null) filters[F.is_active] = isActive\n return filters\n}\n\nexport async function decorateOffersWithDetails(\n items: Record<string, unknown>[],\n ctx: CrudCtx,\n): Promise<void> {\n if (!items.length) return\n const offerIds = items\n .map((item) => (item?.id ? String(item.id) : null))\n .filter((value): value is string => !!value)\n const productIds = items\n .map((item) => (item?.productId ? String(item.productId) : null))\n .filter((value): value is string => !!value)\n if (!offerIds.length && !productIds.length) return\n const em = ctx.container.resolve('em') as EntityManager\n const [products, prices, defaultVariants] = await Promise.all([\n productIds.length\n ? em.find(\n CatalogProduct,\n { id: { $in: productIds } },\n {\n fields: ['id', 'title', 'description', 'defaultMediaId', 'defaultMediaUrl', 'sku'],\n },\n )\n : [],\n offerIds.length\n ? findWithDecryption(\n em,\n CatalogProductPrice,\n { offer: { $in: offerIds } },\n { populate: ['priceKind'] },\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },\n )\n : [],\n productIds.length\n ? em.find(\n CatalogProductVariant,\n { product: { $in: productIds }, isDefault: true },\n { fields: ['id', 'product'] },\n )\n : [],\n ])\n const productMap = new Map(\n products.map((product) => [\n product.id,\n {\n id: product.id,\n title: product.title,\n defaultMediaId: product.defaultMediaId ?? null,\n defaultMediaUrl: product.defaultMediaUrl ?? null,\n sku: product.sku ?? null,\n },\n ]),\n )\n const priceMap = new Map<string, Array<Record<string, unknown>>>()\n prices.forEach((price) => {\n const offerRef = price.offer\n const offerId =\n typeof offerRef === 'string'\n ? offerRef\n : offerRef && typeof offerRef === 'object' && 'id' in offerRef\n ? (offerRef as { id?: string }).id ?? null\n : null\n if (!offerId) return\n const priceKind = price.priceKind\n const priceKindId =\n typeof priceKind === 'string'\n ? priceKind\n : priceKind && typeof priceKind === 'object'\n ? (priceKind as { id?: string }).id ?? null\n : null\n const priceKindCode =\n priceKind && typeof priceKind === 'object' && 'code' in priceKind\n ? (priceKind as { code?: string }).code ?? null\n : null\n const priceKindTitle =\n priceKind && typeof priceKind === 'object' && 'title' in priceKind\n ? (priceKind as { title?: string }).title ?? null\n : null\n const displayMode =\n priceKind && typeof priceKind === 'object' && 'displayMode' in priceKind\n ? (priceKind as { displayMode?: string }).displayMode ?? 'excluding-tax'\n : 'excluding-tax'\n const bucket = priceMap.get(offerId) ?? []\n bucket.push({\n id: price.id,\n priceKindId,\n priceKindCode,\n priceKindTitle,\n currencyCode: price.currencyCode ?? null,\n unitPriceNet: price.unitPriceNet ?? null,\n unitPriceGross: price.unitPriceGross ?? null,\n displayMode,\n minQuantity: price.minQuantity ?? null,\n maxQuantity: price.maxQuantity ?? null,\n })\n priceMap.set(offerId, bucket)\n })\n const variantToProductMap = new Map<string, string>()\n defaultVariants.forEach((variant) => {\n const variantId = typeof variant.id === 'string' ? variant.id : null\n const productRef =\n typeof variant.product === 'string'\n ? variant.product\n : variant.product && typeof variant.product === 'object' && 'id' in variant.product\n ? (variant.product as { id?: string }).id ?? null\n : null\n if (variantId && productRef) {\n variantToProductMap.set(variantId, productRef)\n }\n })\n const DEFAULT_CHANNEL_KEY = '__default__'\n type ProductFallbackPrice = { prices: Record<string, unknown>[]; priority: number }\n const productChannelPriceMap = new Map<string, Map<string, ProductFallbackPrice>>()\n const assignFallbackPrice = (productRef: string | null, channelRef: string | null, payload: Record<string, unknown>, priority: number) => {\n if (!productRef) return\n const bucket = productChannelPriceMap.get(productRef) ?? new Map()\n const effectiveChannel = channelRef ?? DEFAULT_CHANNEL_KEY\n const existing = bucket.get(effectiveChannel)\n if (existing && existing.priority > priority) return\n if (existing && existing.priority === priority) {\n bucket.set(effectiveChannel, { prices: [...existing.prices, payload], priority })\n productChannelPriceMap.set(productRef, bucket)\n return\n }\n bucket.set(effectiveChannel, { prices: [payload], priority })\n productChannelPriceMap.set(productRef, bucket)\n }\n const channelIds = Array.from(new Set(\n items\n .map((item) => {\n if (typeof item?.channelId === 'string') return item.channelId\n if (typeof item?.channel_id === 'string') return item.channel_id\n return null\n })\n .filter((value): value is string => !!value),\n ))\n const channelFilterValues = channelIds.length ? [...channelIds, null] : [null]\n const fallbackTargets: Array<Record<string, unknown>> = []\n if (productIds.length) fallbackTargets.push({ product: { $in: productIds } })\n const defaultVariantIds = Array.from(variantToProductMap.keys())\n if (defaultVariantIds.length) fallbackTargets.push({ variant: { $in: defaultVariantIds } })\n const fallbackEntries = fallbackTargets.length\n ? await findWithDecryption(\n em,\n CatalogProductPrice,\n {\n offer: null,\n $and: [\n { $or: fallbackTargets },\n channelFilterValues.includes(null)\n ? {\n $or: [\n { channelId: { $in: channelFilterValues.filter((id): id is string => typeof id === 'string') } },\n { channelId: null },\n ],\n }\n : { channelId: { $in: channelFilterValues } },\n ],\n },\n { populate: ['priceKind'] },\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },\n )\n : []\n fallbackEntries.forEach((entry) => {\n const entryChannelId = typeof entry.channelId === 'string' && entry.channelId.length\n ? entry.channelId\n : null\n const priceKind = entry.priceKind ?? null\n const priceKindId =\n typeof priceKind?.id === 'string'\n ? priceKind.id\n : null\n const priceKindCode =\n typeof priceKind?.code === 'string'\n ? priceKind.code\n : null\n const priceKindTitle =\n typeof priceKind?.title === 'string'\n ? priceKind.title\n : null\n const displayMode = typeof priceKind?.displayMode === 'string'\n ? priceKind.displayMode\n : typeof (priceKind as any)?.display_mode === 'string'\n ? (priceKind as any).display_mode\n : 'excluding-tax'\n const payload = {\n priceKindId,\n priceKindCode,\n priceKindTitle,\n currencyCode: entry.currencyCode ?? null,\n unitPriceNet: entry.unitPriceNet ?? null,\n unitPriceGross: entry.unitPriceGross ?? null,\n displayMode,\n }\n const variantRef =\n typeof entry.variant === 'string'\n ? entry.variant\n : entry.variant && typeof entry.variant === 'object' && 'id' in entry.variant\n ? (entry.variant as { id?: string }).id ?? null\n : null\n if (variantRef) {\n const variantProduct = variantToProductMap.get(variantRef)\n const productRef =\n variantProduct\n ?? (typeof entry.product === 'string'\n ? entry.product\n : entry.product && typeof entry.product === 'object' && 'id' in entry.product\n ? (entry.product as { id?: string }).id ?? null\n : null)\n const priority = entryChannelId ? 4 : 3\n assignFallbackPrice(productRef, entryChannelId, payload, priority)\n return\n }\n const productRef =\n typeof entry.product === 'string'\n ? entry.product\n : entry.product && typeof entry.product === 'object' && 'id' in entry.product\n ? (entry.product as { id?: string }).id ?? null\n : null\n const priority = entryChannelId ? 2 : 1\n assignFallbackPrice(productRef, entryChannelId, payload, priority)\n })\n items.forEach((item) => {\n const productId = String(item?.productId ?? '')\n item.product = productId ? productMap.get(productId) ?? null : null\n item.prices = priceMap.get(String(item?.id ?? '')) ?? []\n const rowChannelId = typeof item?.channelId === 'string'\n ? item.channelId\n : typeof item?.channel_id === 'string'\n ? item.channel_id\n : null\n const bucket = productChannelPriceMap.get(productId)\n const channelKey = rowChannelId ?? DEFAULT_CHANNEL_KEY\n const channelPrice = bucket?.get(channelKey) ?? null\n const defaultPrice = bucket?.get(DEFAULT_CHANNEL_KEY) ?? null\n const effectivePrice = channelPrice ?? defaultPrice ?? null\n item.productChannelPrice = effectivePrice?.prices?.[0] ?? null\n item.productDefaultPrices = effectivePrice?.prices ?? []\n })\n}\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: CatalogOffer,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: { entityType: E.catalog.catalog_offer },\n list: {\n schema: listSchema,\n entityId: E.catalog.catalog_offer,\n fields: [\n F.id,\n 'product_id',\n F.organization_id,\n F.tenant_id,\n F.channel_id,\n F.title,\n F.description,\n 'default_media_id',\n 'default_media_url',\n F.metadata,\n F.is_active,\n F.created_at,\n F.updated_at,\n ],\n sortFieldMap: {\n title: F.title,\n createdAt: F.created_at,\n updatedAt: F.updated_at,\n },\n buildFilters: async (query) => buildOfferFilters(query),\n transformItem: (item: Record<string, unknown>) => {\n if (!item) return item\n const cfEntries = extractAllCustomFieldEntries(item)\n const base = {\n id: item.id,\n productId: item.product_id ?? null,\n organizationId: item.organization_id ?? null,\n tenantId: item.tenant_id ?? null,\n channelId: item.channel_id ?? null,\n title: item.title ?? '',\n description: item.description ?? null,\n defaultMediaId: item.default_media_id ?? null,\n defaultMediaUrl: item.default_media_url ?? null,\n metadata: item.metadata ?? null,\n isActive: item.is_active ?? false,\n createdAt: item.created_at,\n updatedAt: item.updated_at,\n }\n return Object.keys(cfEntries).length ? { ...base, ...cfEntries } : base\n },\n },\n actions: {\n create: {\n commandId: 'catalog.offers.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(offerCreateSchema, raw ?? {}, ctx, translate)\n },\n response: ({ result }) => ({ id: result?.offerId ?? null }),\n status: 201,\n },\n update: {\n commandId: 'catalog.offers.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(offerUpdateSchema, raw ?? {}, ctx, translate)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'catalog.offers.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n if (!id) {\n throw new CrudHttpError(400, { error: translate('catalog.errors.id_required', 'Offer id is required.') })\n }\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n afterList: async (payload, ctx) => {\n const items = Array.isArray(payload.items) ? payload.items : []\n if (!items.length) return\n await decorateOffersWithDetails(items, ctx)\n },\n },\n})\n\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst offerListItemSchema = z.object({\n id: z.string().uuid(),\n productId: z.string().uuid().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n channelId: z.string().uuid().nullable().optional(),\n title: z.string(),\n description: z.string().nullable().optional(),\n defaultMediaId: z.string().uuid().nullable().optional(),\n defaultMediaUrl: z.string().nullable().optional(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n isActive: z.boolean().nullable().optional(),\n createdAt: z.string().nullable().optional(),\n updatedAt: z.string().nullable().optional(),\n product: z.record(z.string(), z.unknown()).nullable().optional(),\n prices: z.array(z.record(z.string(), z.unknown())).optional(),\n productChannelPrice: z.record(z.string(), z.unknown()).nullable().optional(),\n productDefaultPrices: z.array(z.record(z.string(), z.unknown())).optional(),\n})\n\nexport const openApi = createCatalogCrudOpenApi({\n resourceName: 'Offer',\n pluralName: 'Offers',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(offerListItemSchema),\n create: {\n schema: offerCreateSchema,\n description: 'Creates a new offer linking a product to a sales channel.',\n },\n update: {\n schema: offerUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates an existing offer by id.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes an offer by id.',\n },\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { CatalogOffer, CatalogProduct, CatalogProductPrice, CatalogProductVariant } from '../../data/entities'\nimport { offerCreateSchema, offerUpdateSchema } from '../../data/validators'\nimport { parseScopedCommandInput, resolveCrudRecordId } from '../utils'\nimport { E } from '#generated/entities.ids.generated'\nimport * as F from '#generated/entities/catalog_offer'\nimport { parseIdList } from '../products/route'\nimport { extractAllCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport {\n createCatalogCrudOpenApi,\n createPagedListResponseSchema,\n defaultOkResponseSchema,\n} from '../openapi'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n productId: z.string().uuid().optional(),\n channelId: z.string().uuid().optional(),\n channelIds: z.string().optional(),\n id: z.string().uuid().optional(),\n search: z.string().optional(),\n isActive: z.string().optional(),\n withDeleted: z.coerce.boolean().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\ntype OfferListQuery = z.infer<typeof listSchema>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['sales.channels.manage'] },\n POST: { requireAuth: true, requireFeatures: ['sales.channels.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['sales.channels.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['sales.channels.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nexport function normalizeSearch(term?: string | null): string | null {\n if (!term) return null\n const trimmed = term.trim()\n if (!trimmed.length) return null\n return trimmed\n}\n\nexport function buildOfferFilters(query: OfferListQuery): Record<string, unknown> {\n const filters: Record<string, unknown> = {}\n const searchTerm = normalizeSearch(query.search)\n if (query.id) {\n filters.id = { $eq: query.id }\n }\n if (query.productId) {\n filters.product_id = { $eq: query.productId }\n }\n if (query.channelId) {\n filters.channel_id = { $eq: query.channelId }\n } else {\n const channelIds = parseIdList(query.channelIds)\n if (channelIds.length) {\n filters.channel_id = { $in: channelIds }\n }\n }\n if (searchTerm) {\n const like = `%${escapeLikePattern(searchTerm)}%`\n filters.$or = [{ [F.title]: { $ilike: like } }, { [F.description]: { $ilike: like } }]\n }\n const isActive = parseBooleanToken(query.isActive)\n if (isActive !== null) filters[F.is_active] = isActive\n return filters\n}\n\nexport async function decorateOffersWithDetails(\n items: Record<string, unknown>[],\n ctx: CrudCtx,\n): Promise<void> {\n if (!items.length) return\n const offerIds = items\n .map((item) => (item?.id ? String(item.id) : null))\n .filter((value): value is string => !!value)\n const productIds = items\n .map((item) => (item?.productId ? String(item.productId) : null))\n .filter((value): value is string => !!value)\n if (!offerIds.length && !productIds.length) return\n const em = ctx.container.resolve('em') as EntityManager\n const scopeTenantId = ctx.auth?.tenantId ?? null\n if (!scopeTenantId) {\n throw new CrudHttpError(403, '[internal] Missing tenant scope for offer decoration')\n }\n const scopeOrgIds =\n Array.isArray(ctx.organizationIds) && ctx.organizationIds.length\n ? Array.from(new Set(ctx.organizationIds))\n : (ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)\n ? [(ctx.selectedOrganizationId ?? ctx.auth?.orgId) as string]\n : []\n const scopeWhere: Record<string, unknown> = { tenantId: scopeTenantId }\n if (scopeOrgIds.length === 1) scopeWhere.organizationId = scopeOrgIds[0]\n else if (scopeOrgIds.length > 1) scopeWhere.organizationId = { $in: scopeOrgIds }\n const scope = { tenantId: scopeTenantId, organizationId: scopeOrgIds.length === 1 ? scopeOrgIds[0] : null }\n const [products, prices, defaultVariants] = await Promise.all([\n productIds.length\n ? em.find(\n CatalogProduct,\n { id: { $in: productIds }, ...scopeWhere },\n {\n fields: ['id', 'title', 'description', 'defaultMediaId', 'defaultMediaUrl', 'sku'],\n },\n )\n : [],\n offerIds.length\n ? findWithDecryption(\n em,\n CatalogProductPrice,\n { offer: { $in: offerIds }, ...scopeWhere },\n { populate: ['priceKind'] },\n scope,\n )\n : [],\n productIds.length\n ? em.find(\n CatalogProductVariant,\n { product: { $in: productIds }, isDefault: true, ...scopeWhere },\n { fields: ['id', 'product'] },\n )\n : [],\n ])\n const productMap = new Map(\n products.map((product) => [\n product.id,\n {\n id: product.id,\n title: product.title,\n defaultMediaId: product.defaultMediaId ?? null,\n defaultMediaUrl: product.defaultMediaUrl ?? null,\n sku: product.sku ?? null,\n },\n ]),\n )\n const priceMap = new Map<string, Array<Record<string, unknown>>>()\n prices.forEach((price) => {\n const offerRef = price.offer\n const offerId =\n typeof offerRef === 'string'\n ? offerRef\n : offerRef && typeof offerRef === 'object' && 'id' in offerRef\n ? (offerRef as { id?: string }).id ?? null\n : null\n if (!offerId) return\n const priceKind = price.priceKind\n const priceKindId =\n typeof priceKind === 'string'\n ? priceKind\n : priceKind && typeof priceKind === 'object'\n ? (priceKind as { id?: string }).id ?? null\n : null\n const priceKindCode =\n priceKind && typeof priceKind === 'object' && 'code' in priceKind\n ? (priceKind as { code?: string }).code ?? null\n : null\n const priceKindTitle =\n priceKind && typeof priceKind === 'object' && 'title' in priceKind\n ? (priceKind as { title?: string }).title ?? null\n : null\n const displayMode =\n priceKind && typeof priceKind === 'object' && 'displayMode' in priceKind\n ? (priceKind as { displayMode?: string }).displayMode ?? 'excluding-tax'\n : 'excluding-tax'\n const bucket = priceMap.get(offerId) ?? []\n bucket.push({\n id: price.id,\n priceKindId,\n priceKindCode,\n priceKindTitle,\n currencyCode: price.currencyCode ?? null,\n unitPriceNet: price.unitPriceNet ?? null,\n unitPriceGross: price.unitPriceGross ?? null,\n displayMode,\n minQuantity: price.minQuantity ?? null,\n maxQuantity: price.maxQuantity ?? null,\n })\n priceMap.set(offerId, bucket)\n })\n const variantToProductMap = new Map<string, string>()\n defaultVariants.forEach((variant) => {\n const variantId = typeof variant.id === 'string' ? variant.id : null\n const productRef =\n typeof variant.product === 'string'\n ? variant.product\n : variant.product && typeof variant.product === 'object' && 'id' in variant.product\n ? (variant.product as { id?: string }).id ?? null\n : null\n if (variantId && productRef) {\n variantToProductMap.set(variantId, productRef)\n }\n })\n const DEFAULT_CHANNEL_KEY = '__default__'\n type ProductFallbackPrice = { prices: Record<string, unknown>[]; priority: number }\n const productChannelPriceMap = new Map<string, Map<string, ProductFallbackPrice>>()\n const assignFallbackPrice = (productRef: string | null, channelRef: string | null, payload: Record<string, unknown>, priority: number) => {\n if (!productRef) return\n const bucket = productChannelPriceMap.get(productRef) ?? new Map()\n const effectiveChannel = channelRef ?? DEFAULT_CHANNEL_KEY\n const existing = bucket.get(effectiveChannel)\n if (existing && existing.priority > priority) return\n if (existing && existing.priority === priority) {\n bucket.set(effectiveChannel, { prices: [...existing.prices, payload], priority })\n productChannelPriceMap.set(productRef, bucket)\n return\n }\n bucket.set(effectiveChannel, { prices: [payload], priority })\n productChannelPriceMap.set(productRef, bucket)\n }\n const channelIds = Array.from(new Set(\n items\n .map((item) => {\n if (typeof item?.channelId === 'string') return item.channelId\n if (typeof item?.channel_id === 'string') return item.channel_id\n return null\n })\n .filter((value): value is string => !!value),\n ))\n const channelFilterValues = channelIds.length ? [...channelIds, null] : [null]\n const fallbackTargets: Array<Record<string, unknown>> = []\n if (productIds.length) fallbackTargets.push({ product: { $in: productIds } })\n const defaultVariantIds = Array.from(variantToProductMap.keys())\n if (defaultVariantIds.length) fallbackTargets.push({ variant: { $in: defaultVariantIds } })\n const fallbackEntries = fallbackTargets.length\n ? await findWithDecryption(\n em,\n CatalogProductPrice,\n {\n offer: null,\n ...scopeWhere,\n $and: [\n { $or: fallbackTargets },\n channelFilterValues.includes(null)\n ? {\n $or: [\n { channelId: { $in: channelFilterValues.filter((id): id is string => typeof id === 'string') } },\n { channelId: null },\n ],\n }\n : { channelId: { $in: channelFilterValues } },\n ],\n },\n { populate: ['priceKind'] },\n scope,\n )\n : []\n fallbackEntries.forEach((entry) => {\n const entryChannelId = typeof entry.channelId === 'string' && entry.channelId.length\n ? entry.channelId\n : null\n const priceKind = entry.priceKind ?? null\n const priceKindId =\n typeof priceKind?.id === 'string'\n ? priceKind.id\n : null\n const priceKindCode =\n typeof priceKind?.code === 'string'\n ? priceKind.code\n : null\n const priceKindTitle =\n typeof priceKind?.title === 'string'\n ? priceKind.title\n : null\n const displayMode = typeof priceKind?.displayMode === 'string'\n ? priceKind.displayMode\n : typeof (priceKind as any)?.display_mode === 'string'\n ? (priceKind as any).display_mode\n : 'excluding-tax'\n const payload = {\n priceKindId,\n priceKindCode,\n priceKindTitle,\n currencyCode: entry.currencyCode ?? null,\n unitPriceNet: entry.unitPriceNet ?? null,\n unitPriceGross: entry.unitPriceGross ?? null,\n displayMode,\n }\n const variantRef =\n typeof entry.variant === 'string'\n ? entry.variant\n : entry.variant && typeof entry.variant === 'object' && 'id' in entry.variant\n ? (entry.variant as { id?: string }).id ?? null\n : null\n if (variantRef) {\n const variantProduct = variantToProductMap.get(variantRef)\n const productRef =\n variantProduct\n ?? (typeof entry.product === 'string'\n ? entry.product\n : entry.product && typeof entry.product === 'object' && 'id' in entry.product\n ? (entry.product as { id?: string }).id ?? null\n : null)\n const priority = entryChannelId ? 4 : 3\n assignFallbackPrice(productRef, entryChannelId, payload, priority)\n return\n }\n const productRef =\n typeof entry.product === 'string'\n ? entry.product\n : entry.product && typeof entry.product === 'object' && 'id' in entry.product\n ? (entry.product as { id?: string }).id ?? null\n : null\n const priority = entryChannelId ? 2 : 1\n assignFallbackPrice(productRef, entryChannelId, payload, priority)\n })\n items.forEach((item) => {\n const productId = String(item?.productId ?? '')\n item.product = productId ? productMap.get(productId) ?? null : null\n item.prices = priceMap.get(String(item?.id ?? '')) ?? []\n const rowChannelId = typeof item?.channelId === 'string'\n ? item.channelId\n : typeof item?.channel_id === 'string'\n ? item.channel_id\n : null\n const bucket = productChannelPriceMap.get(productId)\n const channelKey = rowChannelId ?? DEFAULT_CHANNEL_KEY\n const channelPrice = bucket?.get(channelKey) ?? null\n const defaultPrice = bucket?.get(DEFAULT_CHANNEL_KEY) ?? null\n const effectivePrice = channelPrice ?? defaultPrice ?? null\n item.productChannelPrice = effectivePrice?.prices?.[0] ?? null\n item.productDefaultPrices = effectivePrice?.prices ?? []\n })\n}\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: CatalogOffer,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: { entityType: E.catalog.catalog_offer },\n list: {\n schema: listSchema,\n entityId: E.catalog.catalog_offer,\n fields: [\n F.id,\n 'product_id',\n F.organization_id,\n F.tenant_id,\n F.channel_id,\n F.title,\n F.description,\n 'default_media_id',\n 'default_media_url',\n F.metadata,\n F.is_active,\n F.created_at,\n F.updated_at,\n ],\n sortFieldMap: {\n title: F.title,\n createdAt: F.created_at,\n updatedAt: F.updated_at,\n },\n buildFilters: async (query) => buildOfferFilters(query),\n transformItem: (item: Record<string, unknown>) => {\n if (!item) return item\n const cfEntries = extractAllCustomFieldEntries(item)\n const base = {\n id: item.id,\n productId: item.product_id ?? null,\n organizationId: item.organization_id ?? null,\n tenantId: item.tenant_id ?? null,\n channelId: item.channel_id ?? null,\n title: item.title ?? '',\n description: item.description ?? null,\n defaultMediaId: item.default_media_id ?? null,\n defaultMediaUrl: item.default_media_url ?? null,\n metadata: item.metadata ?? null,\n isActive: item.is_active ?? false,\n createdAt: item.created_at,\n updatedAt: item.updated_at,\n }\n return Object.keys(cfEntries).length ? { ...base, ...cfEntries } : base\n },\n },\n actions: {\n create: {\n commandId: 'catalog.offers.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(offerCreateSchema, raw ?? {}, ctx, translate)\n },\n response: ({ result }) => ({ id: result?.offerId ?? null }),\n status: 201,\n },\n update: {\n commandId: 'catalog.offers.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(offerUpdateSchema, raw ?? {}, ctx, translate)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'catalog.offers.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n if (!id) {\n throw new CrudHttpError(400, { error: translate('catalog.errors.id_required', 'Offer id is required.') })\n }\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n afterList: async (payload, ctx) => {\n const items = Array.isArray(payload.items) ? payload.items : []\n if (!items.length) return\n await decorateOffersWithDetails(items, ctx)\n },\n },\n})\n\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst offerListItemSchema = z.object({\n id: z.string().uuid(),\n productId: z.string().uuid().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n channelId: z.string().uuid().nullable().optional(),\n title: z.string(),\n description: z.string().nullable().optional(),\n defaultMediaId: z.string().uuid().nullable().optional(),\n defaultMediaUrl: z.string().nullable().optional(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n isActive: z.boolean().nullable().optional(),\n createdAt: z.string().nullable().optional(),\n updatedAt: z.string().nullable().optional(),\n product: z.record(z.string(), z.unknown()).nullable().optional(),\n prices: z.array(z.record(z.string(), z.unknown())).optional(),\n productChannelPrice: z.record(z.string(), z.unknown()).nullable().optional(),\n productDefaultPrices: z.array(z.record(z.string(), z.unknown())).optional(),\n})\n\nexport const openApi = createCatalogCrudOpenApi({\n resourceName: 'Offer',\n pluralName: 'Offers',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(offerListItemSchema),\n create: {\n schema: offerCreateSchema,\n description: 'Creates a new offer linking a product to a sales channel.',\n },\n update: {\n schema: offerUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates an existing offer by id.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes an offer by id.',\n },\n})\n"],
5
+ "mappings": "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",
6
6
  "names": ["productRef", "priority"]
7
7
  }
@@ -11,6 +11,7 @@ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
11
11
  import { useT } from "@open-mercato/shared/lib/i18n/context";
12
12
  import { SendObjectMessageDialog } from "@open-mercato/ui/backend/messages";
13
13
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
14
+ import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
14
15
  function EditCurrencyPage({ params }) {
15
16
  const t = useT();
16
17
  const router = useRouter();
@@ -18,14 +19,17 @@ function EditCurrencyPage({ params }) {
18
19
  const [currency, setCurrency] = React.useState(null);
19
20
  const [loading, setLoading] = React.useState(true);
20
21
  const [error, setError] = React.useState(null);
22
+ const [isNotFound, setIsNotFound] = React.useState(false);
21
23
  React.useEffect(() => {
22
24
  async function loadCurrency() {
23
25
  try {
24
26
  const response = await apiCall(`/api/currencies/currencies?id=${params?.id}`);
25
27
  if (response.ok && response.result && response.result.items.length > 0) {
26
28
  setCurrency(response.result.items[0]);
29
+ } else if (!response.ok) {
30
+ setError(t("currencies.form.errors.load"));
27
31
  } else {
28
- setError(t("currencies.form.errors.notFound"));
32
+ setIsNotFound(true);
29
33
  }
30
34
  } catch (err) {
31
35
  setError(t("currencies.form.errors.load"));
@@ -132,9 +136,22 @@ function EditCurrencyPage({ params }) {
132
136
  ConfirmDialogElement
133
137
  ] });
134
138
  }
139
+ if (isNotFound) {
140
+ return /* @__PURE__ */ jsxs(Page, { children: [
141
+ /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
142
+ RecordNotFoundState,
143
+ {
144
+ label: t("currencies.form.errors.notFound", "Currency not found."),
145
+ backHref: "/backend/currencies",
146
+ backLabel: t("currencies.form.actions.backToList", "Back to currencies")
147
+ }
148
+ ) }),
149
+ ConfirmDialogElement
150
+ ] });
151
+ }
135
152
  if (error || !currency) {
136
153
  return /* @__PURE__ */ jsxs(Page, { children: [
137
- /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx("div", { className: "text-destructive", children: error || t("currencies.form.errors.notFound") }) }),
154
+ /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ErrorMessage, { label: error ?? t("currencies.form.errors.notFound", "Currency not found.") }) }),
138
155
  ConfirmDialogElement
139
156
  ] });
140
157
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/currencies/backend/currencies/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter, useParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\n\ntype CurrencyData = {\n id: string\n code: string\n name: string\n symbol: string | null\n decimalPlaces: number\n thousandsSeparator: string | null\n decimalSeparator: string | null\n isBase: boolean\n isActive: boolean\n organizationId: string\n tenantId: string\n}\n\nexport default function EditCurrencyPage({ params }: { params?: { id?: string } }) {\n const t = useT()\n const router = useRouter()\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n\n const [currency, setCurrency] = React.useState<CurrencyData | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n async function loadCurrency() {\n try {\n const response = await apiCall<{ items: CurrencyData[] }>(`/api/currencies/currencies?id=${params?.id}`)\n if (response.ok && response.result && response.result.items.length > 0) {\n setCurrency(response.result.items[0])\n } else {\n setError(t('currencies.form.errors.notFound'))\n }\n } catch (err) {\n setError(t('currencies.form.errors.load'))\n } finally {\n setLoading(false)\n }\n }\n loadCurrency()\n }, [params, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'basic',\n column: 1,\n title: t('currencies.form.group.details'),\n fields: [\n {\n id: 'code',\n type: 'text',\n label: t('currencies.form.field.code'),\n placeholder: t('currencies.form.field.codePlaceholder'),\n required: true,\n maxLength: 3,\n helpText: t('currencies.form.field.codeHelp'),\n },\n {\n id: 'name',\n type: 'text',\n label: t('currencies.form.field.name'),\n placeholder: t('currencies.form.field.namePlaceholder'),\n required: true,\n },\n {\n id: 'symbol',\n type: 'text',\n label: t('currencies.form.field.symbol'),\n placeholder: t('currencies.form.field.symbolPlaceholder'),\n },\n ],\n },\n {\n id: 'formatting',\n column: 2,\n title: t('currencies.form.group.formatting'),\n fields: [\n {\n id: 'decimalPlaces',\n type: 'number',\n label: t('currencies.form.field.decimalPlaces'),\n min: 0,\n max: 8,\n },\n {\n id: 'thousandsSeparator',\n type: 'text',\n label: t('currencies.form.field.thousandsSeparator'),\n placeholder: ',',\n maxLength: 5,\n },\n {\n id: 'decimalSeparator',\n type: 'text',\n label: t('currencies.form.field.decimalSeparator'),\n placeholder: '.',\n maxLength: 5,\n },\n {\n id: 'isBase',\n type: 'checkbox',\n label: t('currencies.form.field.isBase'),\n },\n {\n id: 'isActive',\n type: 'checkbox',\n label: t('currencies.form.field.isActive'),\n },\n ],\n },\n ],\n [t]\n )\n\n const handleDelete = React.useCallback(async () => {\n if (!currency) return\n\n const confirmed = await confirmDialog({\n title: t('currencies.list.confirmDelete', { code: currency.code }),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n await apiCall('/api/currencies/currencies', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: currency.id, organizationId: currency.organizationId, tenantId: currency.tenantId }),\n })\n\n flash(t('currencies.flash.deleted'), 'success')\n router.push('/backend/currencies')\n } catch (error) {\n flash(t('currencies.flash.deleteError'), 'error')\n }\n }, [currency, t, router, confirmDialog])\n\n if (loading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex items-center justify-center p-8\">\n <div className=\"text-muted-foreground\">{t('currencies.form.loading')}</div>\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (error || !currency) {\n return (\n <Page>\n <PageBody>\n <div className=\"text-destructive\">{error || t('currencies.form.errors.notFound')}</div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('currencies.edit.title')}\n backHref=\"/backend/currencies\"\n versionHistory={{ resourceKind: 'currencies.currency', resourceId: currency.id }}\n extraActions={(\n <SendObjectMessageDialog\n object={{\n entityModule: 'currencies',\n entityType: 'currency',\n entityId: currency.id,\n previewData: {\n title: currency.name,\n subtitle: currency.code,\n metadata: {\n [t('currencies.form.field.code')]: currency.code,\n [t('currencies.form.field.name')]: currency.name,\n [t('currencies.form.field.symbol')]: currency.symbol || '-',\n },\n },\n }}\n viewHref={`/backend/currencies/${currency.id}`}\n />\n )}\n fields={[]}\n groups={groups}\n initialValues={{\n code: currency.code,\n name: currency.name,\n symbol: currency.symbol || '',\n decimalPlaces: currency.decimalPlaces,\n thousandsSeparator: currency.thousandsSeparator || '',\n decimalSeparator: currency.decimalSeparator || '',\n isBase: currency.isBase,\n isActive: currency.isActive,\n }}\n submitLabel={t('currencies.form.action.save')}\n cancelHref=\"/backend/currencies\"\n onSubmit={async (values) => {\n // Validate currency code\n const code = String(values.code || '').trim().toUpperCase()\n if (!/^[A-Z]{3}$/.test(code)) {\n throw createCrudFormError(t('currencies.form.errors.codeFormat'), {\n code: t('currencies.form.errors.codeFormat'),\n })\n }\n\n const payload = {\n id: currency.id,\n code,\n name: String(values.name || '').trim(),\n symbol: values.symbol ? String(values.symbol).trim() : null,\n decimalPlaces: values.decimalPlaces ? parseInt(String(values.decimalPlaces)) : 2,\n thousandsSeparator: values.thousandsSeparator ? String(values.thousandsSeparator) : null,\n decimalSeparator: values.decimalSeparator ? String(values.decimalSeparator) : null,\n isBase: !!values.isBase,\n isActive: values.isActive !== false,\n }\n\n await updateCrud('currencies/currencies', payload)\n\n flash(t('currencies.flash.updated'), 'success')\n router.push('/backend/currencies')\n }}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter, useParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype CurrencyData = {\n id: string\n code: string\n name: string\n symbol: string | null\n decimalPlaces: number\n thousandsSeparator: string | null\n decimalSeparator: string | null\n isBase: boolean\n isActive: boolean\n organizationId: string\n tenantId: string\n}\n\nexport default function EditCurrencyPage({ params }: { params?: { id?: string } }) {\n const t = useT()\n const router = useRouter()\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n\n const [currency, setCurrency] = React.useState<CurrencyData | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n\n React.useEffect(() => {\n async function loadCurrency() {\n try {\n const response = await apiCall<{ items: CurrencyData[] }>(`/api/currencies/currencies?id=${params?.id}`)\n if (response.ok && response.result && response.result.items.length > 0) {\n setCurrency(response.result.items[0])\n } else if (!response.ok) {\n setError(t('currencies.form.errors.load'))\n } else {\n setIsNotFound(true)\n }\n } catch (err) {\n setError(t('currencies.form.errors.load'))\n } finally {\n setLoading(false)\n }\n }\n loadCurrency()\n }, [params, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'basic',\n column: 1,\n title: t('currencies.form.group.details'),\n fields: [\n {\n id: 'code',\n type: 'text',\n label: t('currencies.form.field.code'),\n placeholder: t('currencies.form.field.codePlaceholder'),\n required: true,\n maxLength: 3,\n helpText: t('currencies.form.field.codeHelp'),\n },\n {\n id: 'name',\n type: 'text',\n label: t('currencies.form.field.name'),\n placeholder: t('currencies.form.field.namePlaceholder'),\n required: true,\n },\n {\n id: 'symbol',\n type: 'text',\n label: t('currencies.form.field.symbol'),\n placeholder: t('currencies.form.field.symbolPlaceholder'),\n },\n ],\n },\n {\n id: 'formatting',\n column: 2,\n title: t('currencies.form.group.formatting'),\n fields: [\n {\n id: 'decimalPlaces',\n type: 'number',\n label: t('currencies.form.field.decimalPlaces'),\n min: 0,\n max: 8,\n },\n {\n id: 'thousandsSeparator',\n type: 'text',\n label: t('currencies.form.field.thousandsSeparator'),\n placeholder: ',',\n maxLength: 5,\n },\n {\n id: 'decimalSeparator',\n type: 'text',\n label: t('currencies.form.field.decimalSeparator'),\n placeholder: '.',\n maxLength: 5,\n },\n {\n id: 'isBase',\n type: 'checkbox',\n label: t('currencies.form.field.isBase'),\n },\n {\n id: 'isActive',\n type: 'checkbox',\n label: t('currencies.form.field.isActive'),\n },\n ],\n },\n ],\n [t]\n )\n\n const handleDelete = React.useCallback(async () => {\n if (!currency) return\n\n const confirmed = await confirmDialog({\n title: t('currencies.list.confirmDelete', { code: currency.code }),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n await apiCall('/api/currencies/currencies', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: currency.id, organizationId: currency.organizationId, tenantId: currency.tenantId }),\n })\n\n flash(t('currencies.flash.deleted'), 'success')\n router.push('/backend/currencies')\n } catch (error) {\n flash(t('currencies.flash.deleteError'), 'error')\n }\n }, [currency, t, router, confirmDialog])\n\n if (loading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex items-center justify-center p-8\">\n <div className=\"text-muted-foreground\">{t('currencies.form.loading')}</div>\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('currencies.form.errors.notFound', 'Currency not found.')}\n backHref=\"/backend/currencies\"\n backLabel={t('currencies.form.actions.backToList', 'Back to currencies')}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (error || !currency) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error ?? t('currencies.form.errors.notFound', 'Currency not found.')} />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('currencies.edit.title')}\n backHref=\"/backend/currencies\"\n versionHistory={{ resourceKind: 'currencies.currency', resourceId: currency.id }}\n extraActions={(\n <SendObjectMessageDialog\n object={{\n entityModule: 'currencies',\n entityType: 'currency',\n entityId: currency.id,\n previewData: {\n title: currency.name,\n subtitle: currency.code,\n metadata: {\n [t('currencies.form.field.code')]: currency.code,\n [t('currencies.form.field.name')]: currency.name,\n [t('currencies.form.field.symbol')]: currency.symbol || '-',\n },\n },\n }}\n viewHref={`/backend/currencies/${currency.id}`}\n />\n )}\n fields={[]}\n groups={groups}\n initialValues={{\n code: currency.code,\n name: currency.name,\n symbol: currency.symbol || '',\n decimalPlaces: currency.decimalPlaces,\n thousandsSeparator: currency.thousandsSeparator || '',\n decimalSeparator: currency.decimalSeparator || '',\n isBase: currency.isBase,\n isActive: currency.isActive,\n }}\n submitLabel={t('currencies.form.action.save')}\n cancelHref=\"/backend/currencies\"\n onSubmit={async (values) => {\n // Validate currency code\n const code = String(values.code || '').trim().toUpperCase()\n if (!/^[A-Z]{3}$/.test(code)) {\n throw createCrudFormError(t('currencies.form.errors.codeFormat'), {\n code: t('currencies.form.errors.codeFormat'),\n })\n }\n\n const payload = {\n id: currency.id,\n code,\n name: String(values.name || '').trim(),\n symbol: values.symbol ? String(values.symbol).trim() : null,\n decimalPlaces: values.decimalPlaces ? parseInt(String(values.decimalPlaces)) : 2,\n thousandsSeparator: values.thousandsSeparator ? String(values.thousandsSeparator) : null,\n decimalSeparator: values.decimalSeparator ? String(values.decimalSeparator) : null,\n isBase: !!values.isBase,\n isActive: values.isActive !== false,\n }\n\n await updateCrud('currencies/currencies', payload)\n\n flash(t('currencies.flash.updated'), 'success')\n router.push('/backend/currencies')\n }}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["error"]
7
7
  }
@@ -10,6 +10,7 @@ import { Spinner } from "@open-mercato/ui/primitives/spinner";
10
10
  import { apiCall, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
11
11
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
12
12
  import { useT } from "@open-mercato/shared/lib/i18n/context";
13
+ import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
13
14
  const PORTAL_FEATURES = [
14
15
  { id: "portal.profile.view", labelKey: "customer_accounts.admin.portalFeatures.profile.view", fallback: "View profile", descriptionKey: "customer_accounts.admin.portalFeatures.profile.view.description", descriptionFallback: "Allows viewing own profile information and account details" },
15
16
  { id: "portal.profile.edit", labelKey: "customer_accounts.admin.portalFeatures.profile.edit", fallback: "Edit profile", descriptionKey: "customer_accounts.admin.portalFeatures.profile.edit.description", descriptionFallback: "Allows editing display name and other profile settings" },
@@ -121,9 +122,10 @@ function CustomerRoleDetailPage({ params }) {
121
122
  const [data, setData] = React.useState(null);
122
123
  const [isLoading, setIsLoading] = React.useState(true);
123
124
  const [error, setError] = React.useState(null);
125
+ const [isNotFound, setIsNotFound] = React.useState(false);
124
126
  React.useEffect(() => {
125
127
  if (!id) {
126
- setError(t("customer_accounts.admin.roleDetail.error.notFound", "Role not found"));
128
+ setIsNotFound(true);
127
129
  setIsLoading(false);
128
130
  return;
129
131
  }
@@ -131,6 +133,7 @@ function CustomerRoleDetailPage({ params }) {
131
133
  async function load() {
132
134
  setIsLoading(true);
133
135
  setError(null);
136
+ setIsNotFound(false);
134
137
  try {
135
138
  const payload = await readApiResultOrThrow(
136
139
  `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,
@@ -141,8 +144,12 @@ function CustomerRoleDetailPage({ params }) {
141
144
  setData(payload);
142
145
  } catch (err) {
143
146
  if (cancelled) return;
144
- const message = err instanceof Error ? err.message : t("customer_accounts.admin.roleDetail.error.load", "Failed to load role");
145
- setError(message);
147
+ if (err.status === 404) {
148
+ setIsNotFound(true);
149
+ } else {
150
+ const message = err instanceof Error ? err.message : t("customer_accounts.admin.roleDetail.error.load", "Failed to load role");
151
+ setError(message);
152
+ }
146
153
  } finally {
147
154
  if (!cancelled) setIsLoading(false);
148
155
  }
@@ -263,11 +270,24 @@ function CustomerRoleDetailPage({ params }) {
263
270
  /* @__PURE__ */ jsx("span", { children: t("customer_accounts.admin.roleDetail.loading", "Loading role...") })
264
271
  ] }) }) });
265
272
  }
273
+ if (isNotFound) {
274
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
275
+ RecordNotFoundState,
276
+ {
277
+ label: t("customer_accounts.admin.roleDetail.error.notFound", "Role not found"),
278
+ backHref: "/backend/customer_accounts/roles",
279
+ backLabel: t("customer_accounts.admin.roleDetail.actions.backToList", "Back to roles")
280
+ }
281
+ ) }) });
282
+ }
266
283
  if (error || !data) {
267
- return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsxs("div", { className: "flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground", children: [
268
- /* @__PURE__ */ jsx("p", { children: error || t("customer_accounts.admin.roleDetail.error.notFound", "Role not found") }),
269
- /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", children: /* @__PURE__ */ jsx(Link, { href: "/backend/customer_accounts/roles", children: t("customer_accounts.admin.roleDetail.actions.backToList", "Back to roles") }) })
270
- ] }) }) });
284
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
285
+ ErrorMessage,
286
+ {
287
+ label: error ?? t("customer_accounts.admin.roleDetail.error.notFound", "Role not found"),
288
+ action: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/customer_accounts/roles", children: t("customer_accounts.admin.roleDetail.actions.backToList", "Back to roles") }) })
289
+ }
290
+ ) }) });
271
291
  }
272
292
  return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
273
293
  CrudForm,