@open-mercato/core 0.6.7-develop.6606.1.3b1ec9b1ea → 0.6.7-develop.6622.1.4d29ddaca4

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 (102) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/session/refresh.js +12 -11
  3. package/dist/modules/auth/api/session/refresh.js.map +2 -2
  4. package/dist/modules/auth/lib/rateLimitCheck.js +4 -4
  5. package/dist/modules/auth/lib/rateLimitCheck.js.map +2 -2
  6. package/dist/modules/catalog/api/categories/route.js +8 -14
  7. package/dist/modules/catalog/api/categories/route.js.map +2 -2
  8. package/dist/modules/catalog/api/tags/route.js +3 -10
  9. package/dist/modules/catalog/api/tags/route.js.map +2 -2
  10. package/dist/modules/customers/api/interactions/[id]/visibility/route.js +15 -17
  11. package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +2 -2
  12. package/dist/modules/customers/api/people/[id]/email-threads/route.js +6 -4
  13. package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +2 -2
  14. package/dist/modules/customers/api/people/[id]/emails/route.js +19 -17
  15. package/dist/modules/customers/api/people/[id]/emails/route.js.map +2 -2
  16. package/dist/modules/customers/components/detail/CustomFieldValuesList.js +22 -5
  17. package/dist/modules/customers/components/detail/CustomFieldValuesList.js.map +2 -2
  18. package/dist/modules/customers/components/detail/create/CreateDealForm.js +5 -2
  19. package/dist/modules/customers/components/detail/create/CreateDealForm.js.map +2 -2
  20. package/dist/modules/customers/components/detail/customFieldUtils.js +10 -3
  21. package/dist/modules/customers/components/detail/customFieldUtils.js.map +2 -2
  22. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.meta.js +4 -2
  23. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.meta.js.map +2 -2
  24. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.meta.js +4 -2
  25. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.meta.js.map +2 -2
  26. package/dist/modules/integrations/api/[id]/credentials/route.js +10 -7
  27. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  28. package/dist/modules/integrations/api/[id]/health/route.js +6 -4
  29. package/dist/modules/integrations/api/[id]/health/route.js.map +2 -2
  30. package/dist/modules/integrations/api/[id]/route.js +4 -2
  31. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  32. package/dist/modules/integrations/api/[id]/state/route.js +7 -5
  33. package/dist/modules/integrations/api/[id]/state/route.js.map +2 -2
  34. package/dist/modules/integrations/api/[id]/version/route.js +7 -5
  35. package/dist/modules/integrations/api/[id]/version/route.js.map +2 -2
  36. package/dist/modules/integrations/api/logs/route.js +4 -2
  37. package/dist/modules/integrations/api/logs/route.js.map +2 -2
  38. package/dist/modules/integrations/api/route.js +4 -2
  39. package/dist/modules/integrations/api/route.js.map +2 -2
  40. package/dist/modules/integrations/api/umes-read.js +2 -1
  41. package/dist/modules/integrations/api/umes-read.js.map +2 -2
  42. package/dist/modules/integrations/lib/organization-scope.js +12 -0
  43. package/dist/modules/integrations/lib/organization-scope.js.map +7 -0
  44. package/dist/modules/payment_gateways/lib/gateway-service.js +10 -1
  45. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  46. package/dist/modules/sales/api/document-history/route.js +6 -9
  47. package/dist/modules/sales/api/document-history/route.js.map +2 -2
  48. package/dist/modules/sales/api/quotes/accept/route.js +3 -3
  49. package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
  50. package/dist/modules/sales/api/returns/[id]/route.js +6 -9
  51. package/dist/modules/sales/api/returns/[id]/route.js.map +2 -2
  52. package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -2
  53. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  54. package/dist/modules/sales/components/documents/AddressesSection.js +6 -4
  55. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  56. package/dist/modules/sales/workflows.js +1 -1
  57. package/dist/modules/sales/workflows.js.map +1 -1
  58. package/dist/modules/workflows/api/definitions/[id]/route.js +10 -1
  59. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  60. package/dist/modules/workflows/lib/event-trigger-service.js +44 -3
  61. package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
  62. package/package.json +7 -7
  63. package/src/modules/auth/api/session/refresh.ts +19 -11
  64. package/src/modules/auth/lib/rateLimitCheck.ts +4 -4
  65. package/src/modules/catalog/api/categories/route.ts +14 -16
  66. package/src/modules/catalog/api/tags/route.ts +6 -10
  67. package/src/modules/catalog/i18n/pl.json +9 -9
  68. package/src/modules/customer_accounts/i18n/pl.json +129 -129
  69. package/src/modules/customers/api/interactions/[id]/visibility/route.ts +24 -20
  70. package/src/modules/customers/api/people/[id]/email-threads/route.ts +10 -6
  71. package/src/modules/customers/api/people/[id]/emails/route.ts +25 -21
  72. package/src/modules/customers/components/detail/CustomFieldValuesList.tsx +28 -4
  73. package/src/modules/customers/components/detail/create/CreateDealForm.tsx +7 -2
  74. package/src/modules/customers/components/detail/customFieldUtils.ts +15 -3
  75. package/src/modules/data_sync/i18n/pl.json +68 -68
  76. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.meta.ts +4 -2
  77. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.meta.ts +4 -2
  78. package/src/modules/integrations/api/[id]/credentials/route.ts +10 -7
  79. package/src/modules/integrations/api/[id]/health/route.ts +6 -4
  80. package/src/modules/integrations/api/[id]/route.ts +4 -2
  81. package/src/modules/integrations/api/[id]/state/route.ts +7 -5
  82. package/src/modules/integrations/api/[id]/version/route.ts +7 -5
  83. package/src/modules/integrations/api/logs/route.ts +4 -2
  84. package/src/modules/integrations/api/route.ts +4 -2
  85. package/src/modules/integrations/api/umes-read.ts +2 -1
  86. package/src/modules/integrations/i18n/de.json +0 -76
  87. package/src/modules/integrations/i18n/en.json +0 -76
  88. package/src/modules/integrations/i18n/es.json +0 -76
  89. package/src/modules/integrations/i18n/pl.json +0 -76
  90. package/src/modules/integrations/lib/organization-scope.ts +27 -0
  91. package/src/modules/payment_gateways/lib/gateway-service.ts +12 -1
  92. package/src/modules/sales/api/document-history/route.ts +10 -9
  93. package/src/modules/sales/api/quotes/accept/route.ts +3 -3
  94. package/src/modules/sales/api/returns/[id]/route.ts +10 -9
  95. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +26 -19
  96. package/src/modules/sales/components/documents/AddressesSection.tsx +8 -4
  97. package/src/modules/sales/i18n/pl.json +24 -24
  98. package/src/modules/sales/workflows.ts +1 -1
  99. package/src/modules/staff/i18n/pl.json +178 -178
  100. package/src/modules/workflows/api/definitions/[id]/route.ts +14 -1
  101. package/src/modules/workflows/i18n/pl.json +128 -128
  102. package/src/modules/workflows/lib/event-trigger-service.ts +87 -8
@@ -9,6 +9,7 @@ import {
9
9
  runCrudMutationGuardAfterSuccess
10
10
  } from "@open-mercato/shared/lib/crud/mutation-guard";
11
11
  import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
12
+ import { isOrganizationReadAccessAllowed } from "@open-mercato/core/modules/directory/utils/organizationScopeGuard";
12
13
  import { resolveAuthActorId } from "../../../../lib/interactionRequestContext.js";
13
14
  import { CustomerEntity } from "../../../../data/entities.js";
14
15
  const metadata = {
@@ -52,22 +53,7 @@ async function POST(req, context) {
52
53
  const container = await createRequestContainer();
53
54
  const userId = resolveAuthActorId(auth);
54
55
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
55
- const organizationId = scope?.selectedId ?? auth.orgId ?? null;
56
- const guardResult = await validateCrudMutationGuard(container, {
57
- tenantId: auth.tenantId,
58
- organizationId,
59
- userId,
60
- resourceKind: "customers.person",
61
- resourceId: personId,
62
- operation: "custom",
63
- requestMethod: req.method,
64
- requestHeaders: req.headers
65
- });
66
- if (guardResult && !guardResult.ok) {
67
- return NextResponse.json(guardResult.body, { status: guardResult.status });
68
- }
69
56
  const em = container.resolve("em").fork();
70
- const dscope = { tenantId: auth.tenantId, organizationId };
71
57
  const person = await findOneWithDecryption(
72
58
  em,
73
59
  CustomerEntity,
@@ -75,15 +61,31 @@ async function POST(req, context) {
75
61
  id: personId,
76
62
  kind: "person",
77
63
  tenantId: auth.tenantId,
78
- organizationId,
79
64
  deletedAt: null
80
65
  },
81
66
  void 0,
82
- dscope
67
+ { tenantId: auth.tenantId, organizationId: scope?.selectedId ?? auth.orgId ?? null }
83
68
  );
84
69
  if (!person) {
85
70
  return NextResponse.json({ error: "Person not found" }, { status: 404 });
86
71
  }
72
+ const organizationId = person.organizationId ?? null;
73
+ if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId })) {
74
+ return NextResponse.json({ error: "Person not found" }, { status: 404 });
75
+ }
76
+ const guardResult = await validateCrudMutationGuard(container, {
77
+ tenantId: auth.tenantId,
78
+ organizationId,
79
+ userId,
80
+ resourceKind: "customers.person",
81
+ resourceId: personId,
82
+ operation: "custom",
83
+ requestMethod: req.method,
84
+ requestHeaders: req.headers
85
+ });
86
+ if (guardResult && !guardResult.ok) {
87
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
88
+ }
87
89
  const sendAsUserService = container.resolve(
88
90
  "communicationChannelsSendAsUser"
89
91
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/customers/api/people/%5Bid%5D/emails/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n validateCrudMutationGuard,\n runCrudMutationGuardAfterSuccess,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveAuthActorId } from '../../../../lib/interactionRequestContext'\nimport { CustomerEntity } from '../../../../data/entities'\nimport type { SendAsUserService } from '@open-mercato/core/modules/communication_channels/lib/send-as-user'\n\nexport const metadata = {\n path: '/customers/people/[id]/emails',\n POST: {\n requireAuth: true,\n requireFeatures: ['customers.email.compose'],\n },\n}\n\nconst composeSchema = z\n .object({\n userChannelId: z.string().uuid(),\n to: z.array(z.string().email()).min(1).max(50),\n cc: z.array(z.string().email()).max(50).optional(),\n bcc: z.array(z.string().email()).max(50).optional(),\n subject: z.string().min(1).max(500),\n body: z.string().min(1).max(200_000),\n bodyFormat: z.enum(['text', 'html']).default('html'),\n visibility: z.enum(['private', 'shared']).default('private'),\n inReplyTo: z.string().max(500).optional(),\n references: z.array(z.string().max(500)).max(50).optional(),\n parentMessageId: z.string().uuid().optional(),\n })\n .strict()\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nexport async function POST(req: Request, context: RouteContext): Promise<Response> {\n const { id: personId } = await context.params\n if (!z.string().uuid().safeParse(personId).success) {\n return NextResponse.json({ error: 'Invalid person id' }, { status: 400 })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth?.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n let body: z.infer<typeof composeSchema>\n try {\n body = composeSchema.parse(await readJsonSafe(req, null))\n } catch (err) {\n return NextResponse.json(\n { error: err instanceof Error ? err.message : 'Invalid request body' },\n { status: 422 },\n )\n }\n\n const container = await createRequestContainer()\n const userId = resolveAuthActorId(auth)\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId,\n resourceKind: 'customers.person',\n resourceId: personId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n const dscope = { tenantId: auth.tenantId as string, organizationId }\n\n // 1. Verify the Person exists in the caller's tenant.\n // Uses CustomerEntity (kind='person') as the canonical ownership check \u2014\n // the same pattern as the existing [id]/route.ts GET handler.\n const person = await findOneWithDecryption(\n em,\n CustomerEntity,\n {\n id: personId,\n kind: 'person',\n tenantId: auth.tenantId,\n organizationId,\n deletedAt: null,\n } as never,\n undefined,\n dscope,\n )\n if (!person) {\n return NextResponse.json({ error: 'Person not found' }, { status: 404 })\n }\n\n // 2. Call the hub's send-as-user facade in-process (resolved via DI) so the\n // customers module makes no HTTP self-call. `crmVisibility` and `crmPersonId`\n // are injected as channelMetadata so the link-channel-message subscriber can\n // anchor the sent message back to this Person on the\n // `communication_channels.message.sent` event.\n const sendAsUserService = container.resolve(\n 'communicationChannelsSendAsUser',\n ) as SendAsUserService\n\n const sendResult = await sendAsUserService(\n container,\n { userId: auth.sub as string, tenantId: auth.tenantId as string, organizationId, auth },\n {\n userChannelId: body.userChannelId,\n to: body.to,\n cc: body.cc,\n bcc: body.bcc,\n subject: body.subject,\n body: body.bodyFormat === 'html' ? { html: body.body } : { plain: body.body },\n inReplyTo: body.inReplyTo,\n references: body.references,\n parentMessageId: body.parentMessageId,\n channelMetadata: {\n crmVisibility: body.visibility,\n crmPersonId: personId,\n },\n },\n )\n\n if (!sendResult.ok) {\n return NextResponse.json({ error: sendResult.error }, { status: sendResult.status })\n }\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId,\n resourceKind: 'customers.person',\n resourceId: personId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n // Delivery is async (handled by the outbound queue worker), so this is the\n // enqueue time \u2014 not the provider send time \u2014 and the provider's external\n // message id is not known yet (the worker assigns it on successful delivery).\n return NextResponse.json({\n messageId: sendResult.messageId,\n threadId: sendResult.threadId,\n queuedAt: new Date().toISOString(),\n })\n}\n\nexport const openApi = {\n tags: ['Customers', 'Email'],\n methods: {\n POST: {\n summary: 'Compose + send an email anchored to a Person',\n tags: ['Customers', 'Email'],\n responses: [\n { status: 200, description: 'Email queued for send' },\n { status: 400, description: 'Invalid person id' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Missing customers.email.compose feature or mutation guard rejection' },\n { status: 404, description: 'Person or channel not found' },\n { status: 409, description: 'Channel not connected' },\n { status: 422, description: 'Invalid request body' },\n { status: 500, description: 'Send failed' },\n ],\n },\n },\n}\nexport default POST\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport {\n validateCrudMutationGuard,\n runCrudMutationGuardAfterSuccess,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { resolveAuthActorId } from '../../../../lib/interactionRequestContext'\nimport { CustomerEntity } from '../../../../data/entities'\nimport type { SendAsUserService } from '@open-mercato/core/modules/communication_channels/lib/send-as-user'\n\nexport const metadata = {\n path: '/customers/people/[id]/emails',\n POST: {\n requireAuth: true,\n requireFeatures: ['customers.email.compose'],\n },\n}\n\nconst composeSchema = z\n .object({\n userChannelId: z.string().uuid(),\n to: z.array(z.string().email()).min(1).max(50),\n cc: z.array(z.string().email()).max(50).optional(),\n bcc: z.array(z.string().email()).max(50).optional(),\n subject: z.string().min(1).max(500),\n body: z.string().min(1).max(200_000),\n bodyFormat: z.enum(['text', 'html']).default('html'),\n visibility: z.enum(['private', 'shared']).default('private'),\n inReplyTo: z.string().max(500).optional(),\n references: z.array(z.string().max(500)).max(50).optional(),\n parentMessageId: z.string().uuid().optional(),\n })\n .strict()\n\ntype RouteContext = {\n params: Promise<{ id: string }> | { id: string }\n}\n\nexport async function POST(req: Request, context: RouteContext): Promise<Response> {\n const { id: personId } = await context.params\n if (!z.string().uuid().safeParse(personId).success) {\n return NextResponse.json({ error: 'Invalid person id' }, { status: 400 })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub || !auth?.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n let body: z.infer<typeof composeSchema>\n try {\n body = composeSchema.parse(await readJsonSafe(req, null))\n } catch (err) {\n return NextResponse.json(\n { error: err instanceof Error ? err.message : 'Invalid request body' },\n { status: 422 },\n )\n }\n\n const container = await createRequestContainer()\n const userId = resolveAuthActorId(auth)\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n\n const em = (container.resolve('em') as EntityManager).fork()\n\n // 1. Verify the Person exists in the caller's tenant, then fail-closed on the\n // record's own organization \u2014 same pattern as the [id]/route.ts GET handler.\n // Loading by tenant + id (not a hand-rolled selected org) keeps this working\n // under the \"All organizations\" scope; the record's own org then becomes the\n // concrete org the guard and the outbound message are attributed to.\n const person = await findOneWithDecryption(\n em,\n CustomerEntity,\n {\n id: personId,\n kind: 'person',\n tenantId: auth.tenantId,\n deletedAt: null,\n } as never,\n undefined,\n { tenantId: auth.tenantId as string, organizationId: scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null },\n )\n if (!person) {\n return NextResponse.json({ error: 'Person not found' }, { status: 404 })\n }\n const organizationId = (person as { organizationId?: string | null }).organizationId ?? null\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId })) {\n return NextResponse.json({ error: 'Person not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId,\n resourceKind: 'customers.person',\n resourceId: personId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n // 2. Call the hub's send-as-user facade in-process (resolved via DI) so the\n // customers module makes no HTTP self-call. `crmVisibility` and `crmPersonId`\n // are injected as channelMetadata so the link-channel-message subscriber can\n // anchor the sent message back to this Person on the\n // `communication_channels.message.sent` event.\n const sendAsUserService = container.resolve(\n 'communicationChannelsSendAsUser',\n ) as SendAsUserService\n\n const sendResult = await sendAsUserService(\n container,\n { userId: auth.sub as string, tenantId: auth.tenantId as string, organizationId, auth },\n {\n userChannelId: body.userChannelId,\n to: body.to,\n cc: body.cc,\n bcc: body.bcc,\n subject: body.subject,\n body: body.bodyFormat === 'html' ? { html: body.body } : { plain: body.body },\n inReplyTo: body.inReplyTo,\n references: body.references,\n parentMessageId: body.parentMessageId,\n channelMetadata: {\n crmVisibility: body.visibility,\n crmPersonId: personId,\n },\n },\n )\n\n if (!sendResult.ok) {\n return NextResponse.json({ error: sendResult.error }, { status: sendResult.status })\n }\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId,\n userId,\n resourceKind: 'customers.person',\n resourceId: personId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n // Delivery is async (handled by the outbound queue worker), so this is the\n // enqueue time \u2014 not the provider send time \u2014 and the provider's external\n // message id is not known yet (the worker assigns it on successful delivery).\n return NextResponse.json({\n messageId: sendResult.messageId,\n threadId: sendResult.threadId,\n queuedAt: new Date().toISOString(),\n })\n}\n\nexport const openApi = {\n tags: ['Customers', 'Email'],\n methods: {\n POST: {\n summary: 'Compose + send an email anchored to a Person',\n tags: ['Customers', 'Email'],\n responses: [\n { status: 200, description: 'Email queued for send' },\n { status: 400, description: 'Invalid person id' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Missing customers.email.compose feature or mutation guard rejection' },\n { status: 404, description: 'Person or channel not found' },\n { status: 409, description: 'Channel not connected' },\n { status: 422, description: 'Invalid request body' },\n { status: 500, description: 'Send failed' },\n ],\n },\n },\n}\nexport default POST\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -51,9 +51,20 @@ function renderDictionaryValues(values, dictionaryMap, multi) {
51
51
  }
52
52
  );
53
53
  }
54
- function renderPrimitiveValues(value) {
54
+ function resolveOptionLabel(value, options) {
55
+ if (!options?.length || typeof value !== "string") return value;
56
+ const trimmed = value.trim();
57
+ const match = options.find((option) => option.value === trimmed);
58
+ return match ? match.label : value;
59
+ }
60
+ function applyOptionLabels(value, options) {
61
+ if (!options?.length) return value;
62
+ if (Array.isArray(value)) return value.map((entry) => resolveOptionLabel(entry, options));
63
+ return resolveOptionLabel(value, options);
64
+ }
65
+ function renderPrimitiveValues(value, kind) {
55
66
  if (Array.isArray(value)) {
56
- const parts = value.map((entry) => stringifyCustomValue(entry)).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
67
+ const parts = value.map((entry) => stringifyCustomValue(entry, { kind })).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
57
68
  if (!parts.length) return null;
58
69
  return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: parts.map((entry, index) => /* @__PURE__ */ jsx(
59
70
  "span",
@@ -64,7 +75,7 @@ function renderPrimitiveValues(value) {
64
75
  `${entry}-${index}`
65
76
  )) });
66
77
  }
67
- const label = stringifyCustomValue(value).trim();
78
+ const label = stringifyCustomValue(value, { kind }).trim();
68
79
  if (!label.length) return null;
69
80
  return /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: label });
70
81
  }
@@ -109,6 +120,10 @@ function buildDisplayEntries(values, entries, definitions, dictionaryMaps) {
109
120
  definitions.forEach((def, index) => {
110
121
  const normalizedKey = normalizeCustomFieldKey(def.key);
111
122
  if (!normalizedKey) return;
123
+ if (def.listVisible === false) {
124
+ consumedKeys.add(normalizedKey);
125
+ return;
126
+ }
112
127
  const entry = combined.get(normalizedKey);
113
128
  if (!entry || isEmptyCustomValue(entry.value)) return;
114
129
  const label = resolveCustomFieldLabel(entry.label ?? def.label, entry.key);
@@ -120,7 +135,9 @@ function buildDisplayEntries(values, entries, definitions, dictionaryMaps) {
120
135
  label,
121
136
  value: entry.value,
122
137
  dictionaryMap,
123
- multi: def.multi ?? Array.isArray(entry.value)
138
+ multi: def.multi ?? Array.isArray(entry.value),
139
+ kind: def.kind,
140
+ options: def.options
124
141
  });
125
142
  consumedKeys.add(normalizedKey);
126
143
  });
@@ -163,7 +180,7 @@ function CustomFieldValuesList({
163
180
  const prefix = itemKeyPrefix ?? "custom-field";
164
181
  return /* @__PURE__ */ jsx("div", { className: cn("grid gap-3 sm:grid-cols-2", className), children: displayEntries.map((entry, index) => {
165
182
  const dictionaryContent = renderDictionaryValues(entry.value, entry.dictionaryMap, entry.multi);
166
- const primitiveContent = dictionaryContent ?? renderPrimitiveValues(entry.value);
183
+ const primitiveContent = dictionaryContent ?? renderPrimitiveValues(applyOptionLabels(entry.value, entry.options), entry.kind);
167
184
  const content = dictionaryContent ?? primitiveContent ?? /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: resolvedEmptyLabel });
168
185
  return /* @__PURE__ */ jsxs(
169
186
  "div",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/CustomFieldValuesList.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n DictionaryValue,\n type DictionaryMap,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport {\n extractDictionaryValue,\n isEmptyCustomValue,\n normalizeCustomFieldKey,\n resolveCustomFieldLabel,\n stringifyCustomValue,\n} from './customFieldUtils'\nimport type { CustomFieldDisplayResources } from './hooks/useCustomFieldDisplay'\n\ntype CustomFieldEntry = {\n key: string\n value: unknown\n label?: string | null\n}\n\ntype DisplayEntry = {\n id: string\n key: string\n normalizedKey: string\n label: string\n value: unknown\n dictionaryMap: DictionaryMap | null\n multi: boolean\n}\n\ntype CustomFieldValuesListProps = {\n values?: Record<string, unknown> | null\n entries?: CustomFieldEntry[]\n definitions?: CustomFieldDefDto[]\n dictionaryMapsByKey?: Record<string, DictionaryMap>\n resources?: CustomFieldDisplayResources\n emptyLabel?: string\n className?: string\n itemKeyPrefix?: string\n}\n\nfunction defaultEmptyLabel(label?: string): string {\n if (typeof label === 'string' && label.trim().length) return label\n return '\u2014'\n}\n\nfunction ensureDictionaryMap(\n key: string,\n normalizedKey: string,\n dictionaryMaps?: Record<string, DictionaryMap>,\n): DictionaryMap | null {\n if (!dictionaryMaps) return null\n if (dictionaryMaps[key]) return dictionaryMaps[key]\n if (dictionaryMaps[normalizedKey]) return dictionaryMaps[normalizedKey]\n return null\n}\n\nfunction renderDictionaryValues(\n values: unknown,\n dictionaryMap: DictionaryMap | null,\n multi: boolean,\n): React.ReactNode {\n if (!dictionaryMap) return null\n if (multi || Array.isArray(values)) {\n const resolved = (Array.isArray(values) ? values : [values])\n .map((entry) => extractDictionaryValue(entry))\n .filter((entry): entry is string => typeof entry === 'string' && entry.length > 0)\n if (!resolved.length) return null\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {resolved.map((value, index) => (\n <DictionaryValue\n key={`${value}-${index}`}\n value={value}\n map={dictionaryMap}\n className=\"inline-flex items-center gap-1 rounded-full border border-border bg-card px-2 py-1 text-xs\"\n iconWrapperClassName=\"inline-flex h-4 w-4 items-center justify-center rounded-full border border-border bg-background\"\n iconClassName=\"h-3 w-3\"\n colorClassName=\"h-2.5 w-2.5 rounded-full\"\n />\n ))}\n </div>\n )\n }\n const resolved = extractDictionaryValue(values)\n if (!resolved) return null\n return (\n <DictionaryValue\n value={resolved}\n map={dictionaryMap}\n className=\"inline-flex items-center gap-2 text-sm\"\n />\n )\n}\n\nfunction renderPrimitiveValues(value: unknown): React.ReactNode {\n if (Array.isArray(value)) {\n const parts = value\n .map((entry) => stringifyCustomValue(entry))\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0)\n if (!parts.length) return null\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {parts.map((entry, index) => (\n <span\n key={`${entry}-${index}`}\n className=\"inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground\"\n >\n {entry}\n </span>\n ))}\n </div>\n )\n }\n const label = stringifyCustomValue(value).trim()\n if (!label.length) return null\n return <span className=\"text-sm text-foreground\">{label}</span>\n}\n\nfunction buildDisplayEntries(\n values: Record<string, unknown> | null | undefined,\n entries: CustomFieldEntry[] | undefined,\n definitions: CustomFieldDefDto[],\n dictionaryMaps: Record<string, DictionaryMap> | undefined,\n): DisplayEntry[] {\n const combined = new Map<\n string,\n {\n key: string\n value: unknown\n label?: string | null\n }\n >()\n\n if (values) {\n Object.entries(values).forEach(([rawKey, value]) => {\n const normalizedKey = normalizeCustomFieldKey(rawKey)\n if (!normalizedKey) return\n if (!combined.has(normalizedKey)) {\n combined.set(normalizedKey, { key: rawKey, value })\n } else {\n const existing = combined.get(normalizedKey)\n if (existing) {\n existing.value = value\n }\n }\n })\n }\n\n if (entries) {\n entries.forEach((entry) => {\n const normalizedKey = normalizeCustomFieldKey(entry.key)\n if (!normalizedKey) return\n const existing = combined.get(normalizedKey)\n if (existing) {\n combined.set(normalizedKey, {\n key: existing.key,\n value: entry.value,\n label: entry.label ?? existing.label,\n })\n } else {\n combined.set(normalizedKey, {\n key: entry.key,\n value: entry.value,\n label: entry.label,\n })\n }\n })\n }\n\n const ordered: DisplayEntry[] = []\n const consumedKeys = new Set<string>()\n\n definitions.forEach((def, index) => {\n const normalizedKey = normalizeCustomFieldKey(def.key)\n if (!normalizedKey) return\n const entry = combined.get(normalizedKey)\n if (!entry || isEmptyCustomValue(entry.value)) return\n const label = resolveCustomFieldLabel(entry.label ?? def.label, entry.key)\n const dictionaryMap = ensureDictionaryMap(entry.key, normalizedKey, dictionaryMaps)\n ordered.push({\n id: `${normalizedKey}-${index}`,\n key: entry.key,\n normalizedKey,\n label,\n value: entry.value,\n dictionaryMap,\n multi: def.multi ?? Array.isArray(entry.value),\n })\n consumedKeys.add(normalizedKey)\n })\n\n const extras: DisplayEntry[] = []\n combined.forEach((entry, normalizedKey) => {\n if (consumedKeys.has(normalizedKey)) return\n if (isEmptyCustomValue(entry.value)) return\n const label = resolveCustomFieldLabel(entry.label, entry.key)\n const dictionaryMap = ensureDictionaryMap(entry.key, normalizedKey, dictionaryMaps)\n extras.push({\n id: normalizedKey,\n key: entry.key,\n normalizedKey,\n label,\n value: entry.value,\n dictionaryMap,\n multi: Array.isArray(entry.value),\n })\n })\n\n extras.sort((a, b) => a.label.localeCompare(b.label))\n\n return [...ordered, ...extras]\n}\n\nexport function CustomFieldValuesList({\n values = null,\n entries,\n definitions,\n dictionaryMapsByKey,\n resources,\n emptyLabel,\n className,\n itemKeyPrefix,\n}: CustomFieldValuesListProps) {\n const displayEntries = React.useMemo(() => {\n const defs = definitions ?? resources?.definitions ?? []\n const maps = dictionaryMapsByKey ?? resources?.dictionaryMapsByKey ?? {}\n return buildDisplayEntries(values, entries, defs, maps)\n }, [definitions, dictionaryMapsByKey, entries, resources, values])\n\n if (!displayEntries.length) return null\n\n const resolvedEmptyLabel = defaultEmptyLabel(emptyLabel)\n const prefix = itemKeyPrefix ?? 'custom-field'\n\n return (\n <div className={cn('grid gap-3 sm:grid-cols-2', className)}>\n {displayEntries.map((entry, index) => {\n const dictionaryContent = renderDictionaryValues(entry.value, entry.dictionaryMap, entry.multi)\n const primitiveContent = dictionaryContent ?? renderPrimitiveValues(entry.value)\n const content =\n dictionaryContent ??\n primitiveContent ??\n <span className=\"text-sm text-muted-foreground\">{resolvedEmptyLabel}</span>\n return (\n <div\n key={`${prefix}-${entry.normalizedKey}-${index}`}\n className=\"rounded-md border border-border/70 bg-muted/30 px-3 py-2\"\n >\n <div className=\"text-xs font-medium text-muted-foreground\">{entry.label}</div>\n <div className=\"mt-1 text-sm text-foreground\">{content}</div>\n </div>\n )\n })}\n </div>\n )\n}\n\nexport default CustomFieldValuesList\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n DictionaryValue,\n type DictionaryMap,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport {\n extractDictionaryValue,\n isEmptyCustomValue,\n normalizeCustomFieldKey,\n resolveCustomFieldLabel,\n stringifyCustomValue,\n} from './customFieldUtils'\nimport type { CustomFieldDisplayResources } from './hooks/useCustomFieldDisplay'\n\ntype CustomFieldEntry = {\n key: string\n value: unknown\n label?: string | null\n}\n\ntype DisplayEntry = {\n id: string\n key: string\n normalizedKey: string\n label: string\n value: unknown\n dictionaryMap: DictionaryMap | null\n multi: boolean\n kind?: string\n options?: CustomFieldDefDto['options']\n}\n\ntype CustomFieldValuesListProps = {\n values?: Record<string, unknown> | null\n entries?: CustomFieldEntry[]\n definitions?: CustomFieldDefDto[]\n dictionaryMapsByKey?: Record<string, DictionaryMap>\n resources?: CustomFieldDisplayResources\n emptyLabel?: string\n className?: string\n itemKeyPrefix?: string\n}\n\nfunction defaultEmptyLabel(label?: string): string {\n if (typeof label === 'string' && label.trim().length) return label\n return '\u2014'\n}\n\nfunction ensureDictionaryMap(\n key: string,\n normalizedKey: string,\n dictionaryMaps?: Record<string, DictionaryMap>,\n): DictionaryMap | null {\n if (!dictionaryMaps) return null\n if (dictionaryMaps[key]) return dictionaryMaps[key]\n if (dictionaryMaps[normalizedKey]) return dictionaryMaps[normalizedKey]\n return null\n}\n\nfunction renderDictionaryValues(\n values: unknown,\n dictionaryMap: DictionaryMap | null,\n multi: boolean,\n): React.ReactNode {\n if (!dictionaryMap) return null\n if (multi || Array.isArray(values)) {\n const resolved = (Array.isArray(values) ? values : [values])\n .map((entry) => extractDictionaryValue(entry))\n .filter((entry): entry is string => typeof entry === 'string' && entry.length > 0)\n if (!resolved.length) return null\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {resolved.map((value, index) => (\n <DictionaryValue\n key={`${value}-${index}`}\n value={value}\n map={dictionaryMap}\n className=\"inline-flex items-center gap-1 rounded-full border border-border bg-card px-2 py-1 text-xs\"\n iconWrapperClassName=\"inline-flex h-4 w-4 items-center justify-center rounded-full border border-border bg-background\"\n iconClassName=\"h-3 w-3\"\n colorClassName=\"h-2.5 w-2.5 rounded-full\"\n />\n ))}\n </div>\n )\n }\n const resolved = extractDictionaryValue(values)\n if (!resolved) return null\n return (\n <DictionaryValue\n value={resolved}\n map={dictionaryMap}\n className=\"inline-flex items-center gap-2 text-sm\"\n />\n )\n}\n\nfunction resolveOptionLabel(value: unknown, options: CustomFieldDefDto['options']): unknown {\n if (!options?.length || typeof value !== 'string') return value\n const trimmed = value.trim()\n const match = options.find((option) => option.value === trimmed)\n return match ? match.label : value\n}\n\nfunction applyOptionLabels(value: unknown, options: CustomFieldDefDto['options']): unknown {\n if (!options?.length) return value\n if (Array.isArray(value)) return value.map((entry) => resolveOptionLabel(entry, options))\n return resolveOptionLabel(value, options)\n}\n\nfunction renderPrimitiveValues(value: unknown, kind?: string): React.ReactNode {\n if (Array.isArray(value)) {\n const parts = value\n .map((entry) => stringifyCustomValue(entry, { kind }))\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0)\n if (!parts.length) return null\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {parts.map((entry, index) => (\n <span\n key={`${entry}-${index}`}\n className=\"inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground\"\n >\n {entry}\n </span>\n ))}\n </div>\n )\n }\n const label = stringifyCustomValue(value, { kind }).trim()\n if (!label.length) return null\n return <span className=\"text-sm text-foreground\">{label}</span>\n}\n\nfunction buildDisplayEntries(\n values: Record<string, unknown> | null | undefined,\n entries: CustomFieldEntry[] | undefined,\n definitions: CustomFieldDefDto[],\n dictionaryMaps: Record<string, DictionaryMap> | undefined,\n): DisplayEntry[] {\n const combined = new Map<\n string,\n {\n key: string\n value: unknown\n label?: string | null\n }\n >()\n\n if (values) {\n Object.entries(values).forEach(([rawKey, value]) => {\n const normalizedKey = normalizeCustomFieldKey(rawKey)\n if (!normalizedKey) return\n if (!combined.has(normalizedKey)) {\n combined.set(normalizedKey, { key: rawKey, value })\n } else {\n const existing = combined.get(normalizedKey)\n if (existing) {\n existing.value = value\n }\n }\n })\n }\n\n if (entries) {\n entries.forEach((entry) => {\n const normalizedKey = normalizeCustomFieldKey(entry.key)\n if (!normalizedKey) return\n const existing = combined.get(normalizedKey)\n if (existing) {\n combined.set(normalizedKey, {\n key: existing.key,\n value: entry.value,\n label: entry.label ?? existing.label,\n })\n } else {\n combined.set(normalizedKey, {\n key: entry.key,\n value: entry.value,\n label: entry.label,\n })\n }\n })\n }\n\n const ordered: DisplayEntry[] = []\n const consumedKeys = new Set<string>()\n\n definitions.forEach((def, index) => {\n const normalizedKey = normalizeCustomFieldKey(def.key)\n if (!normalizedKey) return\n // Operational fields marked listVisible:false must not render \u2014 neither\n // here nor as a definition-less \"extra\" below, so consume the key anyway.\n if (def.listVisible === false) {\n consumedKeys.add(normalizedKey)\n return\n }\n const entry = combined.get(normalizedKey)\n if (!entry || isEmptyCustomValue(entry.value)) return\n const label = resolveCustomFieldLabel(entry.label ?? def.label, entry.key)\n const dictionaryMap = ensureDictionaryMap(entry.key, normalizedKey, dictionaryMaps)\n ordered.push({\n id: `${normalizedKey}-${index}`,\n key: entry.key,\n normalizedKey,\n label,\n value: entry.value,\n dictionaryMap,\n multi: def.multi ?? Array.isArray(entry.value),\n kind: def.kind,\n options: def.options,\n })\n consumedKeys.add(normalizedKey)\n })\n\n const extras: DisplayEntry[] = []\n combined.forEach((entry, normalizedKey) => {\n if (consumedKeys.has(normalizedKey)) return\n if (isEmptyCustomValue(entry.value)) return\n const label = resolveCustomFieldLabel(entry.label, entry.key)\n const dictionaryMap = ensureDictionaryMap(entry.key, normalizedKey, dictionaryMaps)\n extras.push({\n id: normalizedKey,\n key: entry.key,\n normalizedKey,\n label,\n value: entry.value,\n dictionaryMap,\n multi: Array.isArray(entry.value),\n })\n })\n\n extras.sort((a, b) => a.label.localeCompare(b.label))\n\n return [...ordered, ...extras]\n}\n\nexport function CustomFieldValuesList({\n values = null,\n entries,\n definitions,\n dictionaryMapsByKey,\n resources,\n emptyLabel,\n className,\n itemKeyPrefix,\n}: CustomFieldValuesListProps) {\n const displayEntries = React.useMemo(() => {\n const defs = definitions ?? resources?.definitions ?? []\n const maps = dictionaryMapsByKey ?? resources?.dictionaryMapsByKey ?? {}\n return buildDisplayEntries(values, entries, defs, maps)\n }, [definitions, dictionaryMapsByKey, entries, resources, values])\n\n if (!displayEntries.length) return null\n\n const resolvedEmptyLabel = defaultEmptyLabel(emptyLabel)\n const prefix = itemKeyPrefix ?? 'custom-field'\n\n return (\n <div className={cn('grid gap-3 sm:grid-cols-2', className)}>\n {displayEntries.map((entry, index) => {\n const dictionaryContent = renderDictionaryValues(entry.value, entry.dictionaryMap, entry.multi)\n const primitiveContent =\n dictionaryContent ?? renderPrimitiveValues(applyOptionLabels(entry.value, entry.options), entry.kind)\n const content =\n dictionaryContent ??\n primitiveContent ??\n <span className=\"text-sm text-muted-foreground\">{resolvedEmptyLabel}</span>\n return (\n <div\n key={`${prefix}-${entry.normalizedKey}-${index}`}\n className=\"rounded-md border border-border/70 bg-muted/30 px-3 py-2\"\n >\n <div className=\"text-xs font-medium text-muted-foreground\">{entry.label}</div>\n <div className=\"mt-1 text-sm text-foreground\">{content}</div>\n </div>\n )\n })}\n </div>\n )\n}\n\nexport default CustomFieldValuesList\n"],
5
+ "mappings": "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",
6
6
  "names": ["resolved"]
7
7
  }
@@ -23,14 +23,17 @@ import { EMPTY_VALUES } from "./dealFormTypes.js";
23
23
  const CONTEXT_ID = "customers.deals.create";
24
24
  const DEAL_ENTITY_ID = "customers:customer_deal";
25
25
  const CUSTOM_FIELDS_MANAGE_HREF = `/backend/entities/system/${encodeURIComponent(DEAL_ENTITY_ID)}`;
26
- function CreateDealForm({ returnTo }) {
26
+ function CreateDealForm({ returnTo, initialValues }) {
27
27
  const t = useT();
28
28
  const router = useRouter();
29
29
  const tr = React.useCallback(
30
30
  (key, fallback, params) => translateWithFallback(t, key, fallback, params),
31
31
  [t]
32
32
  );
33
- const [values, setValues] = React.useState(EMPTY_VALUES);
33
+ const [values, setValues] = React.useState(() => {
34
+ const definedSeedEntries = Object.entries(initialValues ?? {}).filter(([, seedValue]) => seedValue !== void 0);
35
+ return { ...EMPTY_VALUES, ...Object.fromEntries(definedSeedEntries) };
36
+ });
34
37
  const [errors, setErrors] = React.useState({});
35
38
  const [isSubmitting, setIsSubmitting] = React.useState(false);
36
39
  const { pipelines, stages, loadStages } = useDealPipelines();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customers/components/detail/create/CreateDealForm.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Briefcase, Save } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { dealFormSchema } from '../DealForm'\nimport { createDictionarySelectLabels } from '../utils'\nimport { DealSectionCard } from './DealSectionCard'\nimport { DealDetailsFields } from './DealDetailsFields'\nimport { DealAssociationsSection } from './DealAssociationsSection'\nimport { DealCreateSidebar } from './DealCreateSidebar'\nimport { useDealPipelines } from './useDealPipelines'\nimport { useDealCustomFields } from './useDealCustomFields'\nimport { EMPTY_VALUES, type BaseValues } from './dealFormTypes'\n\nconst CONTEXT_ID = 'customers.deals.create'\nconst DEAL_ENTITY_ID = 'customers:customer_deal'\nconst CUSTOM_FIELDS_MANAGE_HREF = `/backend/entities/system/${encodeURIComponent(DEAL_ENTITY_ID)}`\n\nexport type CreateDealFormProps = {\n returnTo: string\n}\n\nexport function CreateDealForm({ returnTo }: CreateDealFormProps) {\n const t = useT()\n const router = useRouter()\n const tr = React.useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params),\n [t],\n )\n\n const [values, setValues] = React.useState<BaseValues>(EMPTY_VALUES)\n const [errors, setErrors] = React.useState<Record<string, string>>({})\n const [isSubmitting, setIsSubmitting] = React.useState(false)\n\n const { pipelines, stages, loadStages } = useDealPipelines()\n const {\n customValues,\n customFieldsLoaded,\n customCount,\n handleCustomChange,\n handleCustomAttributesLoaded,\n validateCustomFields,\n collectNormalizedCustomValues,\n } = useDealCustomFields(tr)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: CONTEXT_ID,\n blockedMessage: tr('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const statusLabels = React.useMemo(\n () => createDictionarySelectLabels('deal-statuses', (key, fallback) => tr(key, fallback ?? key)),\n [tr],\n )\n\n const patch = React.useCallback((partial: Partial<BaseValues>) => {\n setValues((current) => ({ ...current, ...partial }))\n }, [])\n\n const handlePipelineChange = React.useCallback(\n (id: string) => {\n patch({ pipelineId: id, pipelineStageId: '' })\n // loadStages resets stages to [] on failure; the rejection is intentionally ignored here.\n loadStages(id).catch(() => {})\n },\n [loadStages, patch],\n )\n\n const handleCancel = React.useCallback(() => {\n router.push(returnTo)\n }, [returnTo, router])\n\n const handleSubmit = React.useCallback(async () => {\n if (isSubmitting) return\n if (!customFieldsLoaded) {\n flash(tr('customers.deals.create.sections.custom.loading', 'Loading custom fields...'), 'error')\n return\n }\n const merged = { ...values, ...customValues }\n const parsed = dealFormSchema.safeParse(merged)\n if (!parsed.success) {\n const fieldErrors: Record<string, string> = {}\n for (const issue of parsed.error.issues) {\n const key = typeof issue.path[0] === 'string' ? issue.path[0] : undefined\n if (key && !fieldErrors[key]) fieldErrors[key] = tr(issue.message, issue.message)\n }\n setErrors(fieldErrors)\n const firstMessage = Object.values(fieldErrors)[0]\n if (firstMessage) flash(firstMessage, 'error')\n return\n }\n\n const customFieldErrors = validateCustomFields(merged)\n if (Object.keys(customFieldErrors).length) {\n setErrors(customFieldErrors)\n const firstMessage = Object.values(customFieldErrors)[0]\n if (firstMessage) flash(firstMessage, 'error')\n return\n }\n\n setErrors({})\n setIsSubmitting(true)\n try {\n const data = parsed.data\n const expectedCloseAt =\n data.expectedCloseAt && data.expectedCloseAt.length\n ? new Date(data.expectedCloseAt).toISOString()\n : undefined\n const payload: Record<string, unknown> = {\n title: data.title,\n status: data.status || undefined,\n pipelineId: data.pipelineId || undefined,\n pipelineStageId: data.pipelineStageId || undefined,\n valueAmount: typeof data.valueAmount === 'number' ? data.valueAmount : undefined,\n valueCurrency: data.valueCurrency || undefined,\n probability: typeof data.probability === 'number' ? data.probability : undefined,\n expectedCloseAt,\n description: data.description && data.description.length ? data.description : undefined,\n personIds: values.personIds.length ? values.personIds : undefined,\n companyIds: values.companyIds.length ? values.companyIds : undefined,\n }\n const custom = collectNormalizedCustomValues(merged)\n if (Object.keys(custom).length) payload.customFields = custom\n\n await runMutation({\n operation: () =>\n createCrud('customers/deals', payload, {\n errorMessage: tr('customers.deals.create.error', 'Failed to create deal.'),\n }),\n context: { formId: CONTEXT_ID, resourceKind: 'customers.deal', retryLastMutation },\n mutationPayload: payload,\n })\n flash(tr('customers.people.detail.deals.success', 'Deal created.'), 'success')\n router.push(returnTo)\n } catch (err) {\n const message = err instanceof Error ? err.message : tr('customers.deals.create.error', 'Failed to create deal.')\n flash(message, 'error')\n } finally {\n setIsSubmitting(false)\n }\n }, [\n collectNormalizedCustomValues,\n customFieldsLoaded,\n customValues,\n isSubmitting,\n retryLastMutation,\n returnTo,\n router,\n runMutation,\n tr,\n validateCustomFields,\n values,\n ])\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n const onFormSubmit = React.useCallback(\n (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n handleSubmit()\n },\n [handleSubmit],\n )\n\n const cancelLabel = tr('customers.deals.create.cancel', 'Cancel')\n const submitLabel = tr('customers.deals.create.submit', 'Create deal')\n const submitDisabled = !customFieldsLoaded\n\n return (\n <form className=\"mx-auto max-w-screen-2xl\" onKeyDown={onKeyDown} onSubmit={onFormSubmit} noValidate>\n <FormHeader\n backHref={returnTo}\n backLabel={tr('customers.deals.create.back', 'Back to deals')}\n />\n\n <div className=\"mt-6 grid grid-cols-1 gap-5 lg:grid-cols-[minmax(0,1fr)_330px]\">\n <div className=\"space-y-4\">\n <DealSectionCard\n icon={Briefcase}\n title={tr('customers.deals.create.title', 'Create deal')}\n subtitle={tr('customers.deals.create.sections.details.subtitle', 'Core opportunity info')}\n actions={\n <>\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={isSubmitting}>\n {cancelLabel}\n </Button>\n <Button type=\"button\" onClick={handleSubmit} disabled={isSubmitting || submitDisabled}>\n {isSubmitting ? <Spinner className=\"size-4\" /> : <Save className=\"size-4\" />}\n {submitLabel}\n </Button>\n </>\n }\n >\n <DealDetailsFields\n values={values}\n errors={errors}\n isSubmitting={isSubmitting}\n patch={patch}\n onPipelineChange={handlePipelineChange}\n pipelines={pipelines}\n stages={stages}\n statusLabels={statusLabels}\n tr={tr}\n />\n </DealSectionCard>\n\n <DealAssociationsSection\n tr={tr}\n personIds={values.personIds}\n companyIds={values.companyIds}\n onPeopleChange={(next) => patch({ personIds: next })}\n onCompaniesChange={(next) => patch({ companyIds: next })}\n disabled={isSubmitting}\n />\n </div>\n\n <DealCreateSidebar\n tr={tr}\n customValues={customValues}\n onCustomChange={handleCustomChange}\n errors={errors}\n disabled={isSubmitting}\n customCount={customCount}\n manageHref={CUSTOM_FIELDS_MANAGE_HREF}\n onCustomLoaded={handleCustomAttributesLoaded}\n />\n </div>\n </form>\n )\n}\n\nexport default CreateDealForm\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Briefcase, Save } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { dealFormSchema } from '../DealForm'\nimport { createDictionarySelectLabels } from '../utils'\nimport { DealSectionCard } from './DealSectionCard'\nimport { DealDetailsFields } from './DealDetailsFields'\nimport { DealAssociationsSection } from './DealAssociationsSection'\nimport { DealCreateSidebar } from './DealCreateSidebar'\nimport { useDealPipelines } from './useDealPipelines'\nimport { useDealCustomFields } from './useDealCustomFields'\nimport { EMPTY_VALUES, type BaseValues } from './dealFormTypes'\n\nconst CONTEXT_ID = 'customers.deals.create'\nconst DEAL_ENTITY_ID = 'customers:customer_deal'\nconst CUSTOM_FIELDS_MANAGE_HREF = `/backend/entities/system/${encodeURIComponent(DEAL_ENTITY_ID)}`\n\nexport type CreateDealFormProps = {\n returnTo: string\n /** Seed values merged over EMPTY_VALUES for the initial form state. Entries set to `undefined` are ignored (the EMPTY_VALUES default wins), so a sparse `Partial<BaseValues>` can never unset a required field. Additive: omitting it preserves current behavior. */\n initialValues?: Partial<BaseValues>\n}\n\nexport function CreateDealForm({ returnTo, initialValues }: CreateDealFormProps) {\n const t = useT()\n const router = useRouter()\n const tr = React.useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params),\n [t],\n )\n\n const [values, setValues] = React.useState<BaseValues>(() => {\n const definedSeedEntries = Object.entries(initialValues ?? {}).filter(([, seedValue]) => seedValue !== undefined)\n return { ...EMPTY_VALUES, ...Object.fromEntries(definedSeedEntries) }\n })\n const [errors, setErrors] = React.useState<Record<string, string>>({})\n const [isSubmitting, setIsSubmitting] = React.useState(false)\n\n const { pipelines, stages, loadStages } = useDealPipelines()\n const {\n customValues,\n customFieldsLoaded,\n customCount,\n handleCustomChange,\n handleCustomAttributesLoaded,\n validateCustomFields,\n collectNormalizedCustomValues,\n } = useDealCustomFields(tr)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: CONTEXT_ID,\n blockedMessage: tr('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const statusLabels = React.useMemo(\n () => createDictionarySelectLabels('deal-statuses', (key, fallback) => tr(key, fallback ?? key)),\n [tr],\n )\n\n const patch = React.useCallback((partial: Partial<BaseValues>) => {\n setValues((current) => ({ ...current, ...partial }))\n }, [])\n\n const handlePipelineChange = React.useCallback(\n (id: string) => {\n patch({ pipelineId: id, pipelineStageId: '' })\n // loadStages resets stages to [] on failure; the rejection is intentionally ignored here.\n loadStages(id).catch(() => {})\n },\n [loadStages, patch],\n )\n\n const handleCancel = React.useCallback(() => {\n router.push(returnTo)\n }, [returnTo, router])\n\n const handleSubmit = React.useCallback(async () => {\n if (isSubmitting) return\n if (!customFieldsLoaded) {\n flash(tr('customers.deals.create.sections.custom.loading', 'Loading custom fields...'), 'error')\n return\n }\n const merged = { ...values, ...customValues }\n const parsed = dealFormSchema.safeParse(merged)\n if (!parsed.success) {\n const fieldErrors: Record<string, string> = {}\n for (const issue of parsed.error.issues) {\n const key = typeof issue.path[0] === 'string' ? issue.path[0] : undefined\n if (key && !fieldErrors[key]) fieldErrors[key] = tr(issue.message, issue.message)\n }\n setErrors(fieldErrors)\n const firstMessage = Object.values(fieldErrors)[0]\n if (firstMessage) flash(firstMessage, 'error')\n return\n }\n\n const customFieldErrors = validateCustomFields(merged)\n if (Object.keys(customFieldErrors).length) {\n setErrors(customFieldErrors)\n const firstMessage = Object.values(customFieldErrors)[0]\n if (firstMessage) flash(firstMessage, 'error')\n return\n }\n\n setErrors({})\n setIsSubmitting(true)\n try {\n const data = parsed.data\n const expectedCloseAt =\n data.expectedCloseAt && data.expectedCloseAt.length\n ? new Date(data.expectedCloseAt).toISOString()\n : undefined\n const payload: Record<string, unknown> = {\n title: data.title,\n status: data.status || undefined,\n pipelineId: data.pipelineId || undefined,\n pipelineStageId: data.pipelineStageId || undefined,\n valueAmount: typeof data.valueAmount === 'number' ? data.valueAmount : undefined,\n valueCurrency: data.valueCurrency || undefined,\n probability: typeof data.probability === 'number' ? data.probability : undefined,\n expectedCloseAt,\n description: data.description && data.description.length ? data.description : undefined,\n personIds: values.personIds.length ? values.personIds : undefined,\n companyIds: values.companyIds.length ? values.companyIds : undefined,\n }\n const custom = collectNormalizedCustomValues(merged)\n if (Object.keys(custom).length) payload.customFields = custom\n\n await runMutation({\n operation: () =>\n createCrud('customers/deals', payload, {\n errorMessage: tr('customers.deals.create.error', 'Failed to create deal.'),\n }),\n context: { formId: CONTEXT_ID, resourceKind: 'customers.deal', retryLastMutation },\n mutationPayload: payload,\n })\n flash(tr('customers.people.detail.deals.success', 'Deal created.'), 'success')\n router.push(returnTo)\n } catch (err) {\n const message = err instanceof Error ? err.message : tr('customers.deals.create.error', 'Failed to create deal.')\n flash(message, 'error')\n } finally {\n setIsSubmitting(false)\n }\n }, [\n collectNormalizedCustomValues,\n customFieldsLoaded,\n customValues,\n isSubmitting,\n retryLastMutation,\n returnTo,\n router,\n runMutation,\n tr,\n validateCustomFields,\n values,\n ])\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n const onFormSubmit = React.useCallback(\n (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n handleSubmit()\n },\n [handleSubmit],\n )\n\n const cancelLabel = tr('customers.deals.create.cancel', 'Cancel')\n const submitLabel = tr('customers.deals.create.submit', 'Create deal')\n const submitDisabled = !customFieldsLoaded\n\n return (\n <form className=\"mx-auto max-w-screen-2xl\" onKeyDown={onKeyDown} onSubmit={onFormSubmit} noValidate>\n <FormHeader\n backHref={returnTo}\n backLabel={tr('customers.deals.create.back', 'Back to deals')}\n />\n\n <div className=\"mt-6 grid grid-cols-1 gap-5 lg:grid-cols-[minmax(0,1fr)_330px]\">\n <div className=\"space-y-4\">\n <DealSectionCard\n icon={Briefcase}\n title={tr('customers.deals.create.title', 'Create deal')}\n subtitle={tr('customers.deals.create.sections.details.subtitle', 'Core opportunity info')}\n actions={\n <>\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={isSubmitting}>\n {cancelLabel}\n </Button>\n <Button type=\"button\" onClick={handleSubmit} disabled={isSubmitting || submitDisabled}>\n {isSubmitting ? <Spinner className=\"size-4\" /> : <Save className=\"size-4\" />}\n {submitLabel}\n </Button>\n </>\n }\n >\n <DealDetailsFields\n values={values}\n errors={errors}\n isSubmitting={isSubmitting}\n patch={patch}\n onPipelineChange={handlePipelineChange}\n pipelines={pipelines}\n stages={stages}\n statusLabels={statusLabels}\n tr={tr}\n />\n </DealSectionCard>\n\n <DealAssociationsSection\n tr={tr}\n personIds={values.personIds}\n companyIds={values.companyIds}\n onPeopleChange={(next) => patch({ personIds: next })}\n onCompaniesChange={(next) => patch({ companyIds: next })}\n disabled={isSubmitting}\n />\n </div>\n\n <DealCreateSidebar\n tr={tr}\n customValues={customValues}\n onCustomChange={handleCustomChange}\n errors={errors}\n disabled={isSubmitting}\n customCount={customCount}\n manageHref={CUSTOM_FIELDS_MANAGE_HREF}\n onCustomLoaded={handleCustomAttributesLoaded}\n />\n </div>\n </form>\n )\n}\n\nexport default CreateDealForm\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -30,6 +30,7 @@ function resolveCustomFieldLabel(label, key) {
30
30
  }
31
31
  function isEmptyCustomValue(value) {
32
32
  if (value === null || value === void 0) return true;
33
+ if (value === false) return true;
33
34
  if (typeof value === "string") return value.trim().length === 0;
34
35
  if (Array.isArray(value)) return value.length === 0 || value.every((entry) => isEmptyCustomValue(entry));
35
36
  if (typeof value === "object") {
@@ -41,10 +42,12 @@ function isEmptyCustomValue(value) {
41
42
  }
42
43
  return false;
43
44
  }
44
- function stringifyCustomValue(value) {
45
+ const DATE_FIELD_KINDS = /* @__PURE__ */ new Set(["date", "datetime"]);
46
+ const ISO_DATE_PREFIX_RE = /^\d{4}-\d{2}-\d{2}([T ]|$)/;
47
+ function stringifyCustomValue(value, options) {
45
48
  if (value === null || value === void 0) return "";
46
49
  if (Array.isArray(value)) {
47
- const parts = value.map((entry) => stringifyCustomValue(entry)).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
50
+ const parts = value.map((entry) => stringifyCustomValue(entry, options)).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
48
51
  return parts.join(", ");
49
52
  }
50
53
  if (value instanceof Date) {
@@ -54,7 +57,11 @@ function stringifyCustomValue(value) {
54
57
  if (typeof value === "string") {
55
58
  const trimmed = value.trim();
56
59
  if (!trimmed.length) return "";
57
- return formatDateTime(trimmed) ?? trimmed;
60
+ const kind = options?.kind;
61
+ if (kind ? DATE_FIELD_KINDS.has(kind) : ISO_DATE_PREFIX_RE.test(trimmed)) {
62
+ return formatDateTime(trimmed) ?? trimmed;
63
+ }
64
+ return trimmed;
58
65
  }
59
66
  if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
60
67
  return String(value);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/customFieldUtils.ts"],
4
- "sourcesContent": ["\"use client\"\n\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\n\nfunction stripCustomFieldPrefix(value: string): string {\n return value.replace(/^(?:cf|Cf)(?:[_:\\s-]+)/, '')\n}\n\nfunction normalizeCustomFieldToken(value: string): string {\n return stripCustomFieldPrefix(value.trim())\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[\\s-]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '')\n .toLowerCase()\n}\n\nexport function normalizeCustomFieldKey(key: string | null | undefined): string {\n if (typeof key !== 'string') return ''\n return normalizeCustomFieldToken(key)\n}\n\nexport function formatCustomFieldLabel(key: string): string {\n if (!key) return ''\n const spaced = stripCustomFieldPrefix(key.trim())\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n if (!spaced.length) return stripCustomFieldPrefix(key.trim())\n return spaced\n .split(' ')\n .map((part) => (part.length ? part[0].toUpperCase() + part.slice(1) : part))\n .join(' ')\n}\n\nexport function resolveCustomFieldLabel(label: string | null | undefined, key: string): string {\n const fallbackLabel = formatCustomFieldLabel(key)\n if (typeof label !== 'string') return fallbackLabel\n const trimmed = label.trim()\n if (!trimmed.length) return fallbackLabel\n\n const normalizedKey = normalizeCustomFieldKey(key)\n const normalizedLabel = normalizeCustomFieldToken(trimmed)\n if (normalizedKey && normalizedLabel === normalizedKey) {\n return fallbackLabel\n }\n\n return /^(?:cf|Cf)(?:[_:\\s-]+)/.test(trimmed)\n ? formatCustomFieldLabel(trimmed)\n : trimmed\n}\n\nexport function isEmptyCustomValue(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string') return value.trim().length === 0\n if (Array.isArray(value)) return value.length === 0 || value.every((entry) => isEmptyCustomValue(entry))\n if (typeof value === 'object') {\n try {\n return Object.values(value as Record<string, unknown>).every(isEmptyCustomValue)\n } catch {\n return false\n }\n }\n return false\n}\n\nexport function stringifyCustomValue(value: unknown): string {\n if (value === null || value === undefined) return ''\n if (Array.isArray(value)) {\n const parts = value\n .map((entry) => stringifyCustomValue(entry))\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0)\n return parts.join(', ')\n }\n if (value instanceof Date) {\n const iso = value.toISOString()\n return formatDateTime(iso) ?? iso\n }\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return ''\n return formatDateTime(trimmed) ?? trimmed\n }\n if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {\n return String(value)\n }\n if (typeof value === 'object') {\n const record = value as Record<string, unknown>\n const candidate =\n record.label ?? record.name ?? record.title ?? record.value ?? record.id ?? record.key ?? null\n if (typeof candidate === 'string' && candidate.trim().length) {\n return candidate.trim()\n }\n try {\n return JSON.stringify(value)\n } catch {\n return ''\n }\n }\n return ''\n}\n\nexport function extractDictionaryValue(entry: unknown): string | null {\n if (typeof entry === 'string') {\n const trimmed = entry.trim()\n return trimmed.length ? trimmed : null\n }\n if (!entry || typeof entry !== 'object') return null\n const record = entry as Record<string, unknown>\n const candidate = record.value ?? record.name ?? record.id ?? record.key ?? record.label\n if (typeof candidate === 'string') {\n const trimmed = candidate.trim()\n return trimmed.length ? trimmed : null\n }\n return null\n}\n\nexport function normalizeCustomFieldSubmitValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.filter((entry) => entry !== undefined)\n }\n if (value === undefined) return undefined\n return value\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport { formatDateTime } from '@open-mercato/shared/lib/time'\n\nfunction stripCustomFieldPrefix(value: string): string {\n return value.replace(/^(?:cf|Cf)(?:[_:\\s-]+)/, '')\n}\n\nfunction normalizeCustomFieldToken(value: string): string {\n return stripCustomFieldPrefix(value.trim())\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[\\s-]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '')\n .toLowerCase()\n}\n\nexport function normalizeCustomFieldKey(key: string | null | undefined): string {\n if (typeof key !== 'string') return ''\n return normalizeCustomFieldToken(key)\n}\n\nexport function formatCustomFieldLabel(key: string): string {\n if (!key) return ''\n const spaced = stripCustomFieldPrefix(key.trim())\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n if (!spaced.length) return stripCustomFieldPrefix(key.trim())\n return spaced\n .split(' ')\n .map((part) => (part.length ? part[0].toUpperCase() + part.slice(1) : part))\n .join(' ')\n}\n\nexport function resolveCustomFieldLabel(label: string | null | undefined, key: string): string {\n const fallbackLabel = formatCustomFieldLabel(key)\n if (typeof label !== 'string') return fallbackLabel\n const trimmed = label.trim()\n if (!trimmed.length) return fallbackLabel\n\n const normalizedKey = normalizeCustomFieldKey(key)\n const normalizedLabel = normalizeCustomFieldToken(trimmed)\n if (normalizedKey && normalizedLabel === normalizedKey) {\n return fallbackLabel\n }\n\n return /^(?:cf|Cf)(?:[_:\\s-]+)/.test(trimmed)\n ? formatCustomFieldLabel(trimmed)\n : trimmed\n}\n\nexport function isEmptyCustomValue(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (value === false) return true\n if (typeof value === 'string') return value.trim().length === 0\n if (Array.isArray(value)) return value.length === 0 || value.every((entry) => isEmptyCustomValue(entry))\n if (typeof value === 'object') {\n try {\n return Object.values(value as Record<string, unknown>).every(isEmptyCustomValue)\n } catch {\n return false\n }\n }\n return false\n}\n\nconst DATE_FIELD_KINDS = new Set(['date', 'datetime'])\nconst ISO_DATE_PREFIX_RE = /^\\d{4}-\\d{2}-\\d{2}([T ]|$)/\n\nexport function stringifyCustomValue(value: unknown, options?: { kind?: string }): string {\n if (value === null || value === undefined) return ''\n if (Array.isArray(value)) {\n const parts = value\n .map((entry) => stringifyCustomValue(entry, options))\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0)\n return parts.join(', ')\n }\n if (value instanceof Date) {\n const iso = value.toISOString()\n return formatDateTime(iso) ?? iso\n }\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return ''\n // Date-format strings only when the definition says the field is a date,\n // or (for definition-less extras) when the value is unambiguously ISO-like.\n // `new Date(...)` parses far too loosely to run on every string \u2014 it used\n // to mangle plain text such as \"Report 2024\" into a date.\n const kind = options?.kind\n if (kind ? DATE_FIELD_KINDS.has(kind) : ISO_DATE_PREFIX_RE.test(trimmed)) {\n return formatDateTime(trimmed) ?? trimmed\n }\n return trimmed\n }\n if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {\n return String(value)\n }\n if (typeof value === 'object') {\n const record = value as Record<string, unknown>\n const candidate =\n record.label ?? record.name ?? record.title ?? record.value ?? record.id ?? record.key ?? null\n if (typeof candidate === 'string' && candidate.trim().length) {\n return candidate.trim()\n }\n try {\n return JSON.stringify(value)\n } catch {\n return ''\n }\n }\n return ''\n}\n\nexport function extractDictionaryValue(entry: unknown): string | null {\n if (typeof entry === 'string') {\n const trimmed = entry.trim()\n return trimmed.length ? trimmed : null\n }\n if (!entry || typeof entry !== 'object') return null\n const record = entry as Record<string, unknown>\n const candidate = record.value ?? record.name ?? record.id ?? record.key ?? record.label\n if (typeof candidate === 'string') {\n const trimmed = candidate.trim()\n return trimmed.length ? trimmed : null\n }\n return null\n}\n\nexport function normalizeCustomFieldSubmitValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.filter((entry) => entry !== undefined)\n }\n if (value === undefined) return undefined\n return value\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,11 +2,13 @@ const metadata = {
2
2
  requireAuth: true,
3
3
  requireFeatures: ["directory.organizations.manage"],
4
4
  pageTitle: "Edit Organization",
5
+ pageTitleKey: "directory.organizations.form.title.edit",
5
6
  pageGroup: "Directory",
7
+ pageGroupKey: "directory.nav.group",
6
8
  navHidden: true,
7
9
  breadcrumb: [
8
- { label: "Organizations", href: "/backend/directory/organizations" },
9
- { label: "Edit" }
10
+ { label: "Organizations", labelKey: "directory.nav.organizations", href: "/backend/directory/organizations" },
11
+ { label: "Edit", labelKey: "common.edit" }
10
12
  ]
11
13
  };
12
14
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/directory/backend/directory/organizations/%5Bid%5D/edit/page.meta.ts"],
4
- "sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['directory.organizations.manage'],\n pageTitle: 'Edit Organization',\n pageGroup: 'Directory',\n navHidden: true,\n breadcrumb: [\n { label: 'Organizations', href: '/backend/directory/organizations' },\n { label: 'Edit' },\n ],\n}\n\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,gCAAgC;AAAA,EAClD,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,iBAAiB,MAAM,mCAAmC;AAAA,IACnE,EAAE,OAAO,OAAO;AAAA,EAClB;AACF;",
4
+ "sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['directory.organizations.manage'],\n pageTitle: 'Edit Organization',\n pageTitleKey: 'directory.organizations.form.title.edit',\n pageGroup: 'Directory',\n pageGroupKey: 'directory.nav.group',\n navHidden: true,\n breadcrumb: [\n { label: 'Organizations', labelKey: 'directory.nav.organizations', href: '/backend/directory/organizations' },\n { label: 'Edit', labelKey: 'common.edit' },\n ],\n}\n\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,gCAAgC;AAAA,EAClD,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,iBAAiB,UAAU,+BAA+B,MAAM,mCAAmC;AAAA,IAC5G,EAAE,OAAO,QAAQ,UAAU,cAAc;AAAA,EAC3C;AACF;",
6
6
  "names": []
7
7
  }
@@ -2,11 +2,13 @@ const metadata = {
2
2
  requireAuth: true,
3
3
  requireFeatures: ["directory.tenants.manage"],
4
4
  pageTitle: "Edit Tenant",
5
+ pageTitleKey: "directory.tenants.form.title.edit",
5
6
  pageGroup: "Directory",
7
+ pageGroupKey: "directory.nav.group",
6
8
  navHidden: true,
7
9
  breadcrumb: [
8
- { label: "Tenants", href: "/backend/directory/tenants" },
9
- { label: "Edit" }
10
+ { label: "Tenants", labelKey: "directory.nav.tenants", href: "/backend/directory/tenants" },
11
+ { label: "Edit", labelKey: "common.edit" }
10
12
  ]
11
13
  };
12
14
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/directory/backend/directory/tenants/%5Bid%5D/edit/page.meta.ts"],
4
- "sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['directory.tenants.manage'],\n pageTitle: 'Edit Tenant',\n pageGroup: 'Directory',\n navHidden: true,\n breadcrumb: [\n { label: 'Tenants', href: '/backend/directory/tenants' },\n { label: 'Edit' },\n ],\n}\n\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,0BAA0B;AAAA,EAC5C,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,WAAW,MAAM,6BAA6B;AAAA,IACvD,EAAE,OAAO,OAAO;AAAA,EAClB;AACF;",
4
+ "sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['directory.tenants.manage'],\n pageTitle: 'Edit Tenant',\n pageTitleKey: 'directory.tenants.form.title.edit',\n pageGroup: 'Directory',\n pageGroupKey: 'directory.nav.group',\n navHidden: true,\n breadcrumb: [\n { label: 'Tenants', labelKey: 'directory.nav.tenants', href: '/backend/directory/tenants' },\n { label: 'Edit', labelKey: 'common.edit' },\n ],\n}\n\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,0BAA0B;AAAA,EAC5C,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,WAAW,UAAU,yBAAyB,MAAM,6BAA6B;AAAA,IAC1F,EAAE,OAAO,QAAQ,UAAU,cAAc;AAAA,EAC3C;AACF;",
6
6
  "names": []
7
7
  }
@@ -20,6 +20,7 @@ import {
20
20
  runIntegrationMutationGuardAfterSuccess,
21
21
  runIntegrationMutationGuards
22
22
  } from "../../guards.js";
23
+ import { resolveIntegrationsOrganizationId } from "../../../lib/organization-scope.js";
23
24
  const idParamsSchema = z.object({ id: z.string().min(1) });
24
25
  const metadata = {
25
26
  GET: { requireAuth: true, requireFeatures: ["integrations.credentials.manage"] },
@@ -38,7 +39,8 @@ function resolveParams(ctx) {
38
39
  }
39
40
  async function GET(req, ctx) {
40
41
  const auth = await getAuthFromRequest(req);
41
- if (!auth?.tenantId || !auth.orgId) {
42
+ const organizationId = resolveIntegrationsOrganizationId(auth);
43
+ if (!auth?.tenantId || !organizationId) {
42
44
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
43
45
  }
44
46
  const rawParams = await resolveParams(ctx);
@@ -52,7 +54,7 @@ async function GET(req, ctx) {
52
54
  }
53
55
  const container = await createRequestContainer();
54
56
  const credentialsService = container.resolve("integrationCredentialsService");
55
- const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
57
+ const scope = { organizationId, tenantId: auth.tenantId };
56
58
  let values;
57
59
  let updatedAt;
58
60
  try {
@@ -76,7 +78,8 @@ async function GET(req, ctx) {
76
78
  }
77
79
  async function PUT(req, ctx) {
78
80
  const auth = await getAuthFromRequest(req);
79
- if (!auth?.tenantId || !auth.orgId) {
81
+ const organizationId = resolveIntegrationsOrganizationId(auth);
82
+ if (!auth?.tenantId || !organizationId) {
80
83
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
81
84
  }
82
85
  const rawParams = await resolveParams(ctx);
@@ -98,7 +101,7 @@ async function PUT(req, ctx) {
98
101
  container,
99
102
  {
100
103
  tenantId: auth.tenantId,
101
- organizationId: auth.orgId,
104
+ organizationId,
102
105
  userId: auth.sub ?? "",
103
106
  resourceKind: "integrations.integration",
104
107
  resourceId: integration.id,
@@ -122,7 +125,7 @@ async function PUT(req, ctx) {
122
125
  payloadData = reparsed.data;
123
126
  }
124
127
  const credentialsService = container.resolve("integrationCredentialsService");
125
- const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
128
+ const scope = { organizationId, tenantId: auth.tenantId };
126
129
  const schema = credentialsService.getSchema(integration.id);
127
130
  try {
128
131
  const currentUpdatedAt = await credentialsService.resolveUpdatedAt(integration.id, scope);
@@ -164,12 +167,12 @@ async function PUT(req, ctx) {
164
167
  await emitIntegrationsEvent("integrations.credentials.updated", {
165
168
  integrationId: integration.id,
166
169
  tenantId: auth.tenantId,
167
- organizationId: auth.orgId,
170
+ organizationId,
168
171
  userId: auth.sub
169
172
  });
170
173
  await runIntegrationMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
171
174
  tenantId: auth.tenantId,
172
- organizationId: auth.orgId,
175
+ organizationId,
173
176
  userId: auth.sub ?? "",
174
177
  resourceKind: "integrations.integration",
175
178
  resourceId: integration.id,