@open-mercato/core 0.5.1-develop.2638.59e6e26f46 → 0.5.1-develop.2657.a01847a9fa

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 (58) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +26 -0
  3. package/dist/modules/auth/lib/backendChrome.js +3 -1
  4. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  5. package/dist/modules/auth/services/rbacService.js +8 -2
  6. package/dist/modules/auth/services/rbacService.js.map +2 -2
  7. package/dist/modules/customer_accounts/api/password/reset-confirm.js +7 -0
  8. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
  9. package/dist/modules/customer_accounts/api/portal/nav.js +77 -0
  10. package/dist/modules/customer_accounts/api/portal/nav.js.map +7 -0
  11. package/dist/modules/customer_accounts/api/signup.js +20 -8
  12. package/dist/modules/customer_accounts/api/signup.js.map +2 -2
  13. package/dist/modules/customer_accounts/services/customerSessionService.js +32 -0
  14. package/dist/modules/customer_accounts/services/customerSessionService.js.map +2 -2
  15. package/dist/modules/directory/api/organizations/route.js +10 -0
  16. package/dist/modules/directory/api/organizations/route.js.map +3 -3
  17. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +13 -2
  18. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  19. package/dist/modules/directory/backend/directory/organizations/create/page.js +12 -2
  20. package/dist/modules/directory/backend/directory/organizations/create/page.js.map +2 -2
  21. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js +4 -3
  22. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js.map +2 -2
  23. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.meta.js +17 -0
  24. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.meta.js.map +7 -0
  25. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.meta.js +11 -0
  26. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.meta.js.map +7 -0
  27. package/dist/modules/portal/frontend/[orgSlug]/portal/page.meta.js +11 -0
  28. package/dist/modules/portal/frontend/[orgSlug]/portal/page.meta.js.map +7 -0
  29. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.meta.js +17 -0
  30. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.meta.js.map +7 -0
  31. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.meta.js +11 -0
  32. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.meta.js.map +7 -0
  33. package/dist/modules/portal/frontend/[orgSlug]/portal/verify/page.meta.js +11 -0
  34. package/dist/modules/portal/frontend/[orgSlug]/portal/verify/page.meta.js.map +7 -0
  35. package/dist/modules/workflows/lib/activity-executor.js +25 -16
  36. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  37. package/package.json +3 -3
  38. package/src/modules/auth/lib/backendChrome.tsx +3 -1
  39. package/src/modules/auth/services/rbacService.ts +8 -2
  40. package/src/modules/customer_accounts/api/password/reset-confirm.ts +9 -0
  41. package/src/modules/customer_accounts/api/portal/nav.ts +87 -0
  42. package/src/modules/customer_accounts/api/signup.ts +23 -7
  43. package/src/modules/customer_accounts/services/customerSessionService.ts +39 -0
  44. package/src/modules/directory/api/organizations/route.ts +11 -0
  45. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +17 -3
  46. package/src/modules/directory/backend/directory/organizations/create/page.tsx +15 -3
  47. package/src/modules/directory/i18n/de.json +2 -0
  48. package/src/modules/directory/i18n/en.json +2 -0
  49. package/src/modules/directory/i18n/es.json +2 -0
  50. package/src/modules/directory/i18n/pl.json +2 -0
  51. package/src/modules/messages/components/message-detail/hooks/useMessageDetails.ts +4 -3
  52. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.meta.ts +15 -0
  53. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.meta.ts +9 -0
  54. package/src/modules/portal/frontend/[orgSlug]/portal/page.meta.ts +9 -0
  55. package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.meta.ts +15 -0
  56. package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.meta.ts +9 -0
  57. package/src/modules/portal/frontend/[orgSlug]/portal/verify/page.meta.ts +9 -0
  58. package/src/modules/workflows/lib/activity-executor.ts +52 -24
@@ -1,9 +1,18 @@
1
1
  import { CustomerUser, CustomerUserSession } from "@open-mercato/core/modules/customer_accounts/data/entities";
2
2
  import { generateSecureToken, hashToken } from "@open-mercato/core/modules/customer_accounts/lib/tokenGenerator";
3
3
  import { signAudienceJwt } from "@open-mercato/shared/lib/auth/jwt";
4
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
5
  const CUSTOMER_JWT_AUDIENCE = "customer";
5
6
  const CUSTOMER_JWT_TTL_SECONDS = 60 * 60 * 8;
6
7
  const DEFAULT_SESSION_TTL_DAYS = 30;
8
+ const DEFAULT_MAX_SESSIONS_PER_USER = 5;
9
+ function resolveMaxSessionsPerUser() {
10
+ const raw = process.env.MAX_CUSTOMER_SESSIONS_PER_USER;
11
+ if (!raw) return DEFAULT_MAX_SESSIONS_PER_USER;
12
+ const parsed = Number(raw);
13
+ if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MAX_SESSIONS_PER_USER;
14
+ return Math.floor(parsed);
15
+ }
7
16
  class CustomerSessionService {
8
17
  constructor(em) {
9
18
  this.em = em;
@@ -13,6 +22,7 @@ class CustomerSessionService {
13
22
  const tokenHash = hashToken(rawToken);
14
23
  const days = Number(process.env.CUSTOMER_SESSION_TTL_DAYS || DEFAULT_SESSION_TTL_DAYS);
15
24
  const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1e3);
25
+ await this.enforceSessionCap(user.id, user.tenantId, user.organizationId);
16
26
  const session = this.em.create(CustomerUserSession, {
17
27
  user,
18
28
  tokenHash,
@@ -77,6 +87,28 @@ class CustomerSessionService {
77
87
  async revokeSession(sessionId) {
78
88
  await this.em.nativeUpdate(CustomerUserSession, { id: sessionId }, { deletedAt: /* @__PURE__ */ new Date() });
79
89
  }
90
+ async enforceSessionCap(userId, tenantId, organizationId) {
91
+ const cap = resolveMaxSessionsPerUser();
92
+ const existing = await findWithDecryption(
93
+ this.em,
94
+ CustomerUserSession,
95
+ {
96
+ user: userId,
97
+ deletedAt: null,
98
+ expiresAt: { $gt: /* @__PURE__ */ new Date() }
99
+ },
100
+ { orderBy: { createdAt: "asc" } },
101
+ { tenantId, organizationId }
102
+ );
103
+ const toRevoke = existing.length - (cap - 1);
104
+ if (toRevoke <= 0) return;
105
+ const oldestIds = existing.slice(0, toRevoke).map((s) => s.id);
106
+ await this.em.nativeUpdate(
107
+ CustomerUserSession,
108
+ { id: { $in: oldestIds } },
109
+ { deletedAt: /* @__PURE__ */ new Date() }
110
+ );
111
+ }
80
112
  async revokeAllUserSessions(userId) {
81
113
  const now = /* @__PURE__ */ new Date();
82
114
  await this.em.nativeUpdate(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customer_accounts/services/customerSessionService.ts"],
4
- "sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerUser, CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { generateSecureToken, hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'\nimport { signAudienceJwt } from '@open-mercato/shared/lib/auth/jwt'\n\nexport const CUSTOMER_JWT_AUDIENCE = 'customer'\nconst CUSTOMER_JWT_TTL_SECONDS = 60 * 60 * 8\n\nconst DEFAULT_SESSION_TTL_DAYS = 30\n\nexport class CustomerSessionService {\n constructor(private em: EntityManager) {}\n\n async createSession(\n user: CustomerUser,\n resolvedFeatures: string[],\n ip?: string | null,\n userAgent?: string | null,\n ): Promise<{ rawToken: string; jwt: string; session: CustomerUserSession }> {\n const rawToken = generateSecureToken()\n const tokenHash = hashToken(rawToken)\n const days = Number(process.env.CUSTOMER_SESSION_TTL_DAYS || DEFAULT_SESSION_TTL_DAYS)\n const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000)\n\n const session = this.em.create(CustomerUserSession, {\n user,\n tokenHash,\n ipAddress: ip || null,\n userAgent: userAgent || null,\n expiresAt,\n lastUsedAt: new Date(),\n createdAt: new Date(),\n } as any) as CustomerUserSession\n await this.em.persistAndFlush(session)\n\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n\n return { rawToken, jwt, session }\n }\n\n signCustomerJwt(user: CustomerUser, resolvedFeatures: string[], sessionId: string): string {\n return signAudienceJwt(\n CUSTOMER_JWT_AUDIENCE,\n {\n sub: user.id,\n sid: sessionId,\n type: 'customer',\n tenantId: user.tenantId,\n orgId: user.organizationId,\n email: user.email,\n displayName: user.displayName || '',\n customerEntityId: user.customerEntityId || null,\n personEntityId: user.personEntityId || null,\n resolvedFeatures,\n },\n CUSTOMER_JWT_TTL_SECONDS,\n )\n }\n\n async findByToken(rawToken: string, tenantId?: string): Promise<CustomerUserSession | null> {\n const tokenHash = hashToken(rawToken)\n const session = await this.em.findOne(CustomerUserSession, {\n tokenHash,\n deletedAt: null,\n }, { populate: ['user'] })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n const user = session.user as CustomerUser\n if (tenantId && user?.tenantId !== tenantId) return null\n return session\n }\n\n async refreshSession(\n rawToken: string,\n resolvedFeatures: string[],\n ): Promise<{ jwt: string; user: CustomerUser } | null> {\n const session = await this.findByToken(rawToken)\n if (!session) return null\n const user = session.user as CustomerUser\n if (!user || user.deletedAt || !user.isActive) return null\n\n await this.em.nativeUpdate(CustomerUserSession, { id: session.id }, { lastUsedAt: new Date() })\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n return { jwt, user }\n }\n\n async findActiveSessionById(sessionId: string): Promise<CustomerUserSession | null> {\n const session = await this.em.findOne(CustomerUserSession, {\n id: sessionId,\n deletedAt: null,\n })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n return session\n }\n\n async revokeSession(sessionId: string): Promise<void> {\n await this.em.nativeUpdate(CustomerUserSession, { id: sessionId }, { deletedAt: new Date() })\n }\n\n async revokeAllUserSessions(userId: string): Promise<void> {\n const now = new Date()\n await this.em.nativeUpdate(\n CustomerUserSession,\n { user: userId as any, deletedAt: null },\n { deletedAt: now },\n )\n await this.em.nativeUpdate(\n CustomerUser,\n { id: userId },\n { sessionsRevokedAt: now },\n )\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerUser, CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { generateSecureToken, hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'\nimport { signAudienceJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nexport const CUSTOMER_JWT_AUDIENCE = 'customer'\nconst CUSTOMER_JWT_TTL_SECONDS = 60 * 60 * 8\n\nconst DEFAULT_SESSION_TTL_DAYS = 30\nconst DEFAULT_MAX_SESSIONS_PER_USER = 5\n\nfunction resolveMaxSessionsPerUser(): number {\n const raw = process.env.MAX_CUSTOMER_SESSIONS_PER_USER\n if (!raw) return DEFAULT_MAX_SESSIONS_PER_USER\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MAX_SESSIONS_PER_USER\n return Math.floor(parsed)\n}\n\nexport class CustomerSessionService {\n constructor(private em: EntityManager) {}\n\n async createSession(\n user: CustomerUser,\n resolvedFeatures: string[],\n ip?: string | null,\n userAgent?: string | null,\n ): Promise<{ rawToken: string; jwt: string; session: CustomerUserSession }> {\n const rawToken = generateSecureToken()\n const tokenHash = hashToken(rawToken)\n const days = Number(process.env.CUSTOMER_SESSION_TTL_DAYS || DEFAULT_SESSION_TTL_DAYS)\n const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000)\n\n await this.enforceSessionCap(user.id, user.tenantId, user.organizationId)\n\n const session = this.em.create(CustomerUserSession, {\n user,\n tokenHash,\n ipAddress: ip || null,\n userAgent: userAgent || null,\n expiresAt,\n lastUsedAt: new Date(),\n createdAt: new Date(),\n } as any) as CustomerUserSession\n await this.em.persistAndFlush(session)\n\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n\n return { rawToken, jwt, session }\n }\n\n signCustomerJwt(user: CustomerUser, resolvedFeatures: string[], sessionId: string): string {\n return signAudienceJwt(\n CUSTOMER_JWT_AUDIENCE,\n {\n sub: user.id,\n sid: sessionId,\n type: 'customer',\n tenantId: user.tenantId,\n orgId: user.organizationId,\n email: user.email,\n displayName: user.displayName || '',\n customerEntityId: user.customerEntityId || null,\n personEntityId: user.personEntityId || null,\n resolvedFeatures,\n },\n CUSTOMER_JWT_TTL_SECONDS,\n )\n }\n\n async findByToken(rawToken: string, tenantId?: string): Promise<CustomerUserSession | null> {\n const tokenHash = hashToken(rawToken)\n const session = await this.em.findOne(CustomerUserSession, {\n tokenHash,\n deletedAt: null,\n }, { populate: ['user'] })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n const user = session.user as CustomerUser\n if (tenantId && user?.tenantId !== tenantId) return null\n return session\n }\n\n async refreshSession(\n rawToken: string,\n resolvedFeatures: string[],\n ): Promise<{ jwt: string; user: CustomerUser } | null> {\n const session = await this.findByToken(rawToken)\n if (!session) return null\n const user = session.user as CustomerUser\n if (!user || user.deletedAt || !user.isActive) return null\n\n await this.em.nativeUpdate(CustomerUserSession, { id: session.id }, { lastUsedAt: new Date() })\n const jwt = this.signCustomerJwt(user, resolvedFeatures, session.id)\n return { jwt, user }\n }\n\n async findActiveSessionById(sessionId: string): Promise<CustomerUserSession | null> {\n const session = await this.em.findOne(CustomerUserSession, {\n id: sessionId,\n deletedAt: null,\n })\n if (!session) return null\n if (session.expiresAt.getTime() < Date.now()) return null\n return session\n }\n\n async revokeSession(sessionId: string): Promise<void> {\n await this.em.nativeUpdate(CustomerUserSession, { id: sessionId }, { deletedAt: new Date() })\n }\n\n private async enforceSessionCap(\n userId: string,\n tenantId: string,\n organizationId: string,\n ): Promise<void> {\n const cap = resolveMaxSessionsPerUser()\n const existing = await findWithDecryption(\n this.em,\n CustomerUserSession,\n {\n user: userId as any,\n deletedAt: null,\n expiresAt: { $gt: new Date() },\n },\n { orderBy: { createdAt: 'asc' } },\n { tenantId, organizationId },\n )\n const toRevoke = existing.length - (cap - 1)\n if (toRevoke <= 0) return\n const oldestIds = existing.slice(0, toRevoke).map((s) => s.id)\n await this.em.nativeUpdate(\n CustomerUserSession,\n { id: { $in: oldestIds } },\n { deletedAt: new Date() },\n )\n }\n\n async revokeAllUserSessions(userId: string): Promise<void> {\n const now = new Date()\n await this.em.nativeUpdate(\n CustomerUserSession,\n { user: userId as any, deletedAt: null },\n { deletedAt: now },\n )\n await this.em.nativeUpdate(\n CustomerUser,\n { id: userId },\n { sessionsRevokedAt: now },\n )\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -274,6 +274,10 @@ async function GET(req) {
274
274
  if (!byTenant.has(tid)) byTenant.set(tid, []);
275
275
  byTenant.get(tid).push(org);
276
276
  }
277
+ const slugByOrgId2 = /* @__PURE__ */ new Map();
278
+ for (const org of allOrgs) {
279
+ slugByOrgId2.set(String(org.id), org.slug ?? null);
280
+ }
277
281
  const tenantIds = Array.from(byTenant.keys());
278
282
  const tenants = tenantIds.length ? await em.find(Tenant, { id: { $in: tenantIds } }) : [];
279
283
  const tenantNameMap = tenants.reduce((acc, tenant) => {
@@ -345,6 +349,7 @@ async function GET(req) {
345
349
  return {
346
350
  id: node.id,
347
351
  name: node.name,
352
+ slug: slugByOrgId2.get(recordId) ?? null,
348
353
  tenantId: tid,
349
354
  tenantName: tenantNameMap[tid] ?? tid,
350
355
  parentId: node.parentId,
@@ -383,6 +388,10 @@ async function GET(req) {
383
388
  const orgListFilter = { tenant: tenantId, deletedAt: null };
384
389
  const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: "ASC" } });
385
390
  const hierarchy = computeHierarchyForOrganizations(orgs, tenantId);
391
+ const slugByOrgId = /* @__PURE__ */ new Map();
392
+ for (const org of orgs) {
393
+ slugByOrgId.set(String(org.id), org.slug ?? null);
394
+ }
386
395
  const search = (query.search || "").trim().toLowerCase();
387
396
  let rows = hierarchy.ordered;
388
397
  if (status === "active") {
@@ -437,6 +446,7 @@ async function GET(req) {
437
446
  return {
438
447
  id: node.id,
439
448
  name: node.name,
449
+ slug: slugByOrgId.get(recordId) ?? null,
440
450
  tenantId: node.tenantId,
441
451
  tenantName,
442
452
  parentId: node.parentId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/directory/api/organizations/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { organizationCreateSchema, organizationUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport {\n computeHierarchyForOrganizations,\n type ComputedHierarchy,\n type ComputedOrganizationNode,\n} from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport {\n getSelectedOrganizationFromRequest,\n resolveOrganizationScopeForRequest,\n} from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { organizationCrudEvents, organizationCrudIndexer } from '@open-mercato/core/modules/directory/commands/organizations'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport {\n directoryTag,\n directoryErrorSchema,\n directoryOkSchema,\n organizationListResponseSchema,\n} from '../openapi'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\ntype CrudInput = Record<string, unknown>\nconst rawBodySchema = z.object({}).passthrough()\n\ntype TreeNode = {\n id: string\n name: string\n parentId: string | null\n tenantId: string | null\n depth: number\n ancestorIds: string[]\n childIds: string[]\n descendantIds: string[]\n isActive: boolean\n treePath: string | null\n pathLabel: string\n children: TreeNode[]\n}\n\nconst viewSchema = z.object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(200).default(50),\n search: z.string().optional(),\n view: z.enum(['options', 'manage', 'tree']).default('options'),\n ids: z.string().optional(),\n tenantId: z.string().uuid().optional(),\n includeInactive: z.enum(['true', 'false']).optional(),\n status: z.enum(['all', 'active', 'inactive']).optional(),\n})\n\nfunction parseIds(raw: string | null): string[] | null {\n if (!raw) return null\n const ids = raw.split(',').map((s) => s.trim()).filter(Boolean)\n return ids.length ? Array.from(new Set(ids)) : null\n}\n\nfunction stringId(value: unknown): string {\n return String(value)\n}\n\nfunction enforceTenantScope(\n authTenantId: string | null,\n requestedTenantId: string | null,\n isSuperAdmin: boolean,\n): string | null {\n if (isSuperAdmin) {\n return requestedTenantId || authTenantId || null\n }\n if (authTenantId && requestedTenantId && requestedTenantId !== authTenantId) {\n return null\n }\n return requestedTenantId || authTenantId\n}\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: {\n GET: { requireAuth: true, requireFeatures: ['directory.organizations.view'] },\n POST: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n },\n orm: {\n entity: Organization,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: organizationCrudEvents,\n indexer: organizationCrudIndexer,\n actions: {\n create: {\n commandId: 'directory.organizations.create',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: ({ result }) => ({ id: String(result.id) }),\n status: 201,\n },\n update: {\n commandId: 'directory.organizations.update',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'directory.organizations.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport const metadata = crud.metadata\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ items: [] }, { status: 401 })\n\n const url = new URL(req.url)\n const parsed = viewSchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n view: url.searchParams.get('view') ?? undefined,\n ids: url.searchParams.get('ids') ?? undefined,\n tenantId: url.searchParams.get('tenantId') ?? undefined,\n includeInactive: url.searchParams.get('includeInactive') ?? undefined,\n status: url.searchParams.get('status') ?? undefined,\n })\n if (!parsed.success) return NextResponse.json({ items: [] }, { status: 400 })\n\n const query = parsed.data\n const ids = parseIds(query.ids ?? null)\n const requestedTenantRaw = query.tenantId ?? null\n const normalizedRequestedTenantId = requestedTenantRaw && requestedTenantRaw.toLowerCase() === 'all' ? null : requestedTenantRaw\n const authTenantId = auth.tenantId ?? null\n const container = await createRequestContainer()\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const em = (container.resolve('em') as EntityManager)\n const allowAllTenants = isSuperAdmin && !normalizedRequestedTenantId && query.view === 'manage'\n let tenantId = allowAllTenants ? null : enforceTenantScope(authTenantId, normalizedRequestedTenantId, isSuperAdmin)\n const status = query.status ?? 'all'\n const includeInactive = parseBooleanToken(query.includeInactive) === true || status !== 'active'\n\n if (!allowAllTenants && !tenantId && !authTenantId && ids?.length) {\n const scopedOrgs: Organization[] = await findWithDecryption(\n em,\n Organization,\n { id: { $in: ids }, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId: authTenantId, organizationId: null },\n )\n const tenantCandidates = new Set<string>()\n for (const org of scopedOrgs) {\n const orgTenantId = org.tenant?.id ? stringId(org.tenant.id) : ''\n if (orgTenantId) tenantCandidates.add(orgTenantId)\n }\n if (tenantCandidates.size === 1) {\n tenantId = Array.from(tenantCandidates)[0] ?? null\n } else if (tenantCandidates.size > 1) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n }\n\n if (!allowAllTenants && !tenantId) {\n const candidateOrgIds = new Set<string>()\n const cookieOrgId = getSelectedOrganizationFromRequest(req)\n const effectiveCookieOrgId = cookieOrgId && !isAllOrganizationsSelection(cookieOrgId) ? cookieOrgId : null\n if (effectiveCookieOrgId) candidateOrgIds.add(effectiveCookieOrgId)\n if (auth.orgId) candidateOrgIds.add(auth.orgId)\n\n try {\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n selectedId: effectiveCookieOrgId ?? undefined,\n })\n if (scope.selectedId) candidateOrgIds.add(scope.selectedId)\n if (Array.isArray(scope.filterIds) && scope.filterIds.length) {\n candidateOrgIds.add(scope.filterIds[0]!)\n }\n if (Array.isArray(scope.allowedIds) && scope.allowedIds.length) {\n candidateOrgIds.add(scope.allowedIds[0]!)\n }\n } catch {}\n\n for (const orgId of candidateOrgIds) {\n if (!orgId) continue\n const org = await findOneWithDecryption(\n em,\n Organization,\n { id: orgId, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId: authTenantId, organizationId: orgId },\n )\n if (org?.tenant && org.tenant.id) {\n tenantId = stringId(org.tenant.id)\n break\n }\n }\n }\n\n if (!allowAllTenants && !tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n\n if (query.view === 'options') {\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n const where: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n if (status === 'active') where.isActive = true\n if (status === 'inactive') where.isActive = false\n if (status === 'all' && !includeInactive) where.isActive = true\n if (ids) where.id = { $in: ids }\n const orgs = await em.find(Organization, where, { orderBy: { name: 'ASC' } })\n const items = orgs.map((org) => ({\n id: stringId(org.id),\n name: org.name,\n parentId: org.parentId ?? null,\n tenantId: tenantId,\n isActive: !!org.isActive,\n depth: org.depth ?? 0,\n treePath: org.treePath ?? stringId(org.id),\n }))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items })\n }\n\n if (query.view === 'tree') {\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n const orgListFilter: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: 'ASC' } })\n const hierarchy = computeHierarchyForOrganizations(orgs, tenantId)\n const nodeMap = new Map<string, { node: ComputedOrganizationNode; children: TreeNode[] }>()\n const roots: TreeNode[] = []\n for (const node of hierarchy.ordered) {\n const treeNode: TreeNode = {\n id: node.id,\n name: node.name,\n parentId: node.parentId,\n tenantId: node.tenantId,\n depth: node.depth,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n isActive: node.isActive,\n treePath: node.treePath,\n pathLabel: node.pathLabel,\n children: [],\n }\n nodeMap.set(node.id, { node, children: treeNode.children })\n if (node.parentId && nodeMap.has(node.parentId)) {\n const parentEntry = nodeMap.get(node.parentId)!\n parentEntry.children.push(treeNode)\n } else {\n roots.push(treeNode)\n }\n }\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: roots,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n })\n return NextResponse.json({ items: roots })\n }\n\n if (query.view !== 'manage') {\n return NextResponse.json({ items: [] }, { status: 400 })\n }\n\n if (allowAllTenants) {\n // Multi-tenant aggregate view for super administrators\n const search = (query.search || '').trim().toLowerCase()\n const allOrgs = await findWithDecryption(\n em,\n Organization,\n { deletedAt: null },\n { orderBy: { name: 'ASC' }, populate: ['tenant'] },\n { tenantId: null, organizationId: null },\n )\n const byTenant = new Map<string, Organization[]>()\n for (const org of allOrgs) {\n const tenantEntity = org.tenant\n const tid = tenantEntity ? stringId(tenantEntity.id) : null\n if (!tid) continue\n if (!byTenant.has(tid)) byTenant.set(tid, [])\n byTenant.get(tid)!.push(org)\n }\n\n const tenantIds = Array.from(byTenant.keys())\n const tenants = tenantIds.length\n ? await em.find(Tenant, { id: { $in: tenantIds as unknown as string[] } })\n : []\n const tenantNameMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tid = stringId(tenant.id)\n const name = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : tid\n acc[tid] = name\n return acc\n }, {})\n\n const hierarchies = new Map<string, ComputedHierarchy>()\n const orderedNodes: Array<{ tenantId: string; node: ComputedOrganizationNode }> = []\n for (const [tid, orgs] of byTenant.entries()) {\n const hierarchy = computeHierarchyForOrganizations(orgs, tid)\n hierarchies.set(tid, hierarchy)\n for (const node of hierarchy.ordered) {\n orderedNodes.push({ tenantId: tid, node })\n }\n }\n\n let rows = orderedNodes\n if (query.status === 'active') {\n rows = rows.filter((entry) => entry.node.isActive)\n } else if (query.status === 'inactive') {\n rows = rows.filter((entry) => !entry.node.isActive)\n }\n\n if (search) {\n rows = rows.filter(({ node }) => {\n const pathLabel = (node.pathLabel || '').toLowerCase()\n return node.name.toLowerCase().includes(search) || pathLabel.includes(search)\n })\n }\n if (ids) {\n const idSet = new Set(ids)\n rows = rows.filter(({ node }) => idSet.has(node.id))\n }\n\n rows.sort((a, b) => {\n const nameA = tenantNameMap[a.tenantId] ?? a.tenantId\n const nameB = tenantNameMap[b.tenantId] ?? b.tenantId\n const tenantCompare = nameA.localeCompare(nameB)\n if (tenantCompare !== 0) return tenantCompare\n return a.node.pathLabel.localeCompare(b.node.pathLabel)\n })\n\n const total = rows.length\n const pageSize = query.pageSize\n const page = query.page\n const start = (page - 1) * pageSize\n const paged = rows.slice(start, start + pageSize)\n const recordIds: string[] = []\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const entry of paged) {\n const recordId = String(entry.node.id)\n recordIds.push(recordId)\n tenantIdByRecord[recordId] = entry.tenantId\n organizationIdByRecord[recordId] = recordId\n }\n const tenantFallbacks = Array.from(new Set(recordIds.map((id) => tenantIdByRecord[id]).filter((value): value is string => typeof value === 'string' && value.length > 0)))\n const cfByOrg = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.organization,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks,\n })\n : {}\n const items = paged.map(({ tenantId: tid, node }) => {\n const hierarchy = hierarchies.get(tid)\n const parentName = node.parentId && hierarchy ? hierarchy.map.get(node.parentId)?.name ?? null : null\n const pathLabel = node.pathLabel || node.name\n const recordId = String(node.id)\n return {\n id: node.id,\n name: node.name,\n tenantId: tid,\n tenantName: tenantNameMap[tid] ?? tid,\n parentId: node.parentId,\n parentName,\n depth: node.depth,\n rootId: node.rootId,\n treePath: node.treePath,\n pathLabel,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n childrenCount: node.childIds.length,\n descendantsCount: node.descendantIds.length,\n isActive: node.isActive,\n ...(cfByOrg[recordId] ?? {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId: null,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total, page, pageSize, totalPages, isSuperAdmin })\n }\n\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n\n const orgListFilter: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: 'ASC' } })\n const hierarchy = computeHierarchyForOrganizations(orgs, tenantId)\n\n // Manage view: paginated flat list for a single tenant\n const search = (query.search || '').trim().toLowerCase()\n let rows = hierarchy.ordered\n if (status === 'active') {\n rows = rows.filter((node) => node.isActive)\n } else if (status === 'inactive') {\n rows = rows.filter((node) => !node.isActive)\n }\n\n if (search) {\n rows = rows.filter((node) => {\n const pathLabel = (node.pathLabel || '').toLowerCase()\n return node.name.toLowerCase().includes(search) || pathLabel.includes(search)\n })\n }\n if (ids) {\n const idSet = new Set(ids)\n rows = rows.filter((node) => idSet.has(node.id))\n }\n\n const total = rows.length\n const pageSize = query.pageSize\n const page = query.page\n const start = (page - 1) * pageSize\n const paged = rows.slice(start, start + pageSize)\n const recordIds: string[] = []\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const node of paged) {\n const recordId = String(node.id)\n recordIds.push(recordId)\n tenantIdByRecord[recordId] = node.tenantId ? String(node.tenantId) : null\n organizationIdByRecord[recordId] = recordId\n }\n const cfByOrg = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.organization,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks: tenantId ? [tenantId] : [],\n })\n : {}\n let tenantName: string | null = null\n if (isSuperAdmin && tenantId) {\n const tenant = await em.findOne(Tenant, { id: tenantId })\n if (tenant) {\n const display = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : stringId(tenant.id)\n tenantName = display\n }\n }\n const items = paged.map((node) => {\n const parentName = node.parentId ? hierarchy.map.get(node.parentId)?.name ?? null : null\n const pathLabel = node.pathLabel || node.name\n const recordId = String(node.id)\n return {\n id: node.id,\n name: node.name,\n tenantId: node.tenantId,\n tenantName,\n parentId: node.parentId,\n parentName,\n depth: node.depth,\n rootId: node.rootId,\n treePath: node.treePath,\n pathLabel,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n childrenCount: node.childIds.length,\n descendantsCount: node.descendantIds.length,\n isActive: node.isActive,\n ...(cfByOrg[recordId] ?? {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total, page, pageSize, totalPages, isSuperAdmin })\n}\n\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst organizationCreateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst organizationDeleteRequestSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst organizationsGetDoc: OpenApiMethodDoc = {\n summary: 'List organizations',\n description: 'Returns organizations using options, tree, or paginated manage view depending on the `view` parameter.',\n tags: [directoryTag],\n query: viewSchema,\n responses: [\n { status: 200, description: 'Organization data for the requested view.', schema: organizationListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query or tenant scope', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsPostDoc: OpenApiMethodDoc = {\n summary: 'Create organization',\n description: 'Creates a new organization within a tenant and optionally assigns hierarchy relationships.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationCreateSchema,\n description: 'Organization attributes and optional hierarchy configuration.',\n },\n responses: [\n { status: 201, description: 'Organization created.', schema: organizationCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsPutDoc: OpenApiMethodDoc = {\n summary: 'Update organization',\n description: 'Updates organization details and hierarchy assignments.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationUpdateSchema,\n description: 'Organization identifier followed by fields to update.',\n },\n responses: [\n { status: 200, description: 'Organization updated.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete organization',\n description: 'Soft deletes an organization identified by id.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationDeleteRequestSchema,\n description: 'Identifier of the organization to delete.',\n },\n responses: [\n { status: 200, description: 'Organization deleted.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: directoryTag,\n summary: 'Manage organizations',\n methods: {\n GET: organizationsGetDoc,\n POST: organizationsPostDoc,\n PUT: organizationsPutDoc,\n DELETE: organizationsDeleteDoc,\n },\n}\n"],
5
- "mappings": "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",
6
- "names": ["orgs", "items", "orgListFilter", "hierarchy", "search", "rows", "total", "pageSize", "page", "start", "paged", "recordIds", "tenantIdByRecord", "organizationIdByRecord", "cfByOrg", "totalPages"]
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { organizationCreateSchema, organizationUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport {\n computeHierarchyForOrganizations,\n type ComputedHierarchy,\n type ComputedOrganizationNode,\n} from '@open-mercato/core/modules/directory/lib/hierarchy'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport {\n getSelectedOrganizationFromRequest,\n resolveOrganizationScopeForRequest,\n} from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { organizationCrudEvents, organizationCrudIndexer } from '@open-mercato/core/modules/directory/commands/organizations'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport {\n directoryTag,\n directoryErrorSchema,\n directoryOkSchema,\n organizationListResponseSchema,\n} from '../openapi'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\ntype CrudInput = Record<string, unknown>\nconst rawBodySchema = z.object({}).passthrough()\n\ntype TreeNode = {\n id: string\n name: string\n parentId: string | null\n tenantId: string | null\n depth: number\n ancestorIds: string[]\n childIds: string[]\n descendantIds: string[]\n isActive: boolean\n treePath: string | null\n pathLabel: string\n children: TreeNode[]\n}\n\nconst viewSchema = z.object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(200).default(50),\n search: z.string().optional(),\n view: z.enum(['options', 'manage', 'tree']).default('options'),\n ids: z.string().optional(),\n tenantId: z.string().uuid().optional(),\n includeInactive: z.enum(['true', 'false']).optional(),\n status: z.enum(['all', 'active', 'inactive']).optional(),\n})\n\nfunction parseIds(raw: string | null): string[] | null {\n if (!raw) return null\n const ids = raw.split(',').map((s) => s.trim()).filter(Boolean)\n return ids.length ? Array.from(new Set(ids)) : null\n}\n\nfunction stringId(value: unknown): string {\n return String(value)\n}\n\nfunction enforceTenantScope(\n authTenantId: string | null,\n requestedTenantId: string | null,\n isSuperAdmin: boolean,\n): string | null {\n if (isSuperAdmin) {\n return requestedTenantId || authTenantId || null\n }\n if (authTenantId && requestedTenantId && requestedTenantId !== authTenantId) {\n return null\n }\n return requestedTenantId || authTenantId\n}\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: {\n GET: { requireAuth: true, requireFeatures: ['directory.organizations.view'] },\n POST: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['directory.organizations.manage'] },\n },\n orm: {\n entity: Organization,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: organizationCrudEvents,\n indexer: organizationCrudIndexer,\n actions: {\n create: {\n commandId: 'directory.organizations.create',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: ({ result }) => ({ id: String(result.id) }),\n status: 201,\n },\n update: {\n commandId: 'directory.organizations.update',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'directory.organizations.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport const metadata = crud.metadata\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ items: [] }, { status: 401 })\n\n const url = new URL(req.url)\n const parsed = viewSchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n view: url.searchParams.get('view') ?? undefined,\n ids: url.searchParams.get('ids') ?? undefined,\n tenantId: url.searchParams.get('tenantId') ?? undefined,\n includeInactive: url.searchParams.get('includeInactive') ?? undefined,\n status: url.searchParams.get('status') ?? undefined,\n })\n if (!parsed.success) return NextResponse.json({ items: [] }, { status: 400 })\n\n const query = parsed.data\n const ids = parseIds(query.ids ?? null)\n const requestedTenantRaw = query.tenantId ?? null\n const normalizedRequestedTenantId = requestedTenantRaw && requestedTenantRaw.toLowerCase() === 'all' ? null : requestedTenantRaw\n const authTenantId = auth.tenantId ?? null\n const container = await createRequestContainer()\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const em = (container.resolve('em') as EntityManager)\n const allowAllTenants = isSuperAdmin && !normalizedRequestedTenantId && query.view === 'manage'\n let tenantId = allowAllTenants ? null : enforceTenantScope(authTenantId, normalizedRequestedTenantId, isSuperAdmin)\n const status = query.status ?? 'all'\n const includeInactive = parseBooleanToken(query.includeInactive) === true || status !== 'active'\n\n if (!allowAllTenants && !tenantId && !authTenantId && ids?.length) {\n const scopedOrgs: Organization[] = await findWithDecryption(\n em,\n Organization,\n { id: { $in: ids }, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId: authTenantId, organizationId: null },\n )\n const tenantCandidates = new Set<string>()\n for (const org of scopedOrgs) {\n const orgTenantId = org.tenant?.id ? stringId(org.tenant.id) : ''\n if (orgTenantId) tenantCandidates.add(orgTenantId)\n }\n if (tenantCandidates.size === 1) {\n tenantId = Array.from(tenantCandidates)[0] ?? null\n } else if (tenantCandidates.size > 1) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n }\n\n if (!allowAllTenants && !tenantId) {\n const candidateOrgIds = new Set<string>()\n const cookieOrgId = getSelectedOrganizationFromRequest(req)\n const effectiveCookieOrgId = cookieOrgId && !isAllOrganizationsSelection(cookieOrgId) ? cookieOrgId : null\n if (effectiveCookieOrgId) candidateOrgIds.add(effectiveCookieOrgId)\n if (auth.orgId) candidateOrgIds.add(auth.orgId)\n\n try {\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n selectedId: effectiveCookieOrgId ?? undefined,\n })\n if (scope.selectedId) candidateOrgIds.add(scope.selectedId)\n if (Array.isArray(scope.filterIds) && scope.filterIds.length) {\n candidateOrgIds.add(scope.filterIds[0]!)\n }\n if (Array.isArray(scope.allowedIds) && scope.allowedIds.length) {\n candidateOrgIds.add(scope.allowedIds[0]!)\n }\n } catch {}\n\n for (const orgId of candidateOrgIds) {\n if (!orgId) continue\n const org = await findOneWithDecryption(\n em,\n Organization,\n { id: orgId, deletedAt: null },\n { populate: ['tenant'] },\n { tenantId: authTenantId, organizationId: orgId },\n )\n if (org?.tenant && org.tenant.id) {\n tenantId = stringId(org.tenant.id)\n break\n }\n }\n }\n\n if (!allowAllTenants && !tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n\n if (query.view === 'options') {\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n const where: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n if (status === 'active') where.isActive = true\n if (status === 'inactive') where.isActive = false\n if (status === 'all' && !includeInactive) where.isActive = true\n if (ids) where.id = { $in: ids }\n const orgs = await em.find(Organization, where, { orderBy: { name: 'ASC' } })\n const items = orgs.map((org) => ({\n id: stringId(org.id),\n name: org.name,\n parentId: org.parentId ?? null,\n tenantId: tenantId,\n isActive: !!org.isActive,\n depth: org.depth ?? 0,\n treePath: org.treePath ?? stringId(org.id),\n }))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items })\n }\n\n if (query.view === 'tree') {\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n const orgListFilter: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: 'ASC' } })\n const hierarchy = computeHierarchyForOrganizations(orgs, tenantId)\n const nodeMap = new Map<string, { node: ComputedOrganizationNode; children: TreeNode[] }>()\n const roots: TreeNode[] = []\n for (const node of hierarchy.ordered) {\n const treeNode: TreeNode = {\n id: node.id,\n name: node.name,\n parentId: node.parentId,\n tenantId: node.tenantId,\n depth: node.depth,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n isActive: node.isActive,\n treePath: node.treePath,\n pathLabel: node.pathLabel,\n children: [],\n }\n nodeMap.set(node.id, { node, children: treeNode.children })\n if (node.parentId && nodeMap.has(node.parentId)) {\n const parentEntry = nodeMap.get(node.parentId)!\n parentEntry.children.push(treeNode)\n } else {\n roots.push(treeNode)\n }\n }\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: roots,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n })\n return NextResponse.json({ items: roots })\n }\n\n if (query.view !== 'manage') {\n return NextResponse.json({ items: [] }, { status: 400 })\n }\n\n if (allowAllTenants) {\n // Multi-tenant aggregate view for super administrators\n const search = (query.search || '').trim().toLowerCase()\n const allOrgs = await findWithDecryption(\n em,\n Organization,\n { deletedAt: null },\n { orderBy: { name: 'ASC' }, populate: ['tenant'] },\n { tenantId: null, organizationId: null },\n )\n const byTenant = new Map<string, Organization[]>()\n for (const org of allOrgs) {\n const tenantEntity = org.tenant\n const tid = tenantEntity ? stringId(tenantEntity.id) : null\n if (!tid) continue\n if (!byTenant.has(tid)) byTenant.set(tid, [])\n byTenant.get(tid)!.push(org)\n }\n\n const slugByOrgId = new Map<string, string | null>()\n for (const org of allOrgs) {\n slugByOrgId.set(String(org.id), org.slug ?? null)\n }\n\n const tenantIds = Array.from(byTenant.keys())\n const tenants = tenantIds.length\n ? await em.find(Tenant, { id: { $in: tenantIds as unknown as string[] } })\n : []\n const tenantNameMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tid = stringId(tenant.id)\n const name = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : tid\n acc[tid] = name\n return acc\n }, {})\n\n const hierarchies = new Map<string, ComputedHierarchy>()\n const orderedNodes: Array<{ tenantId: string; node: ComputedOrganizationNode }> = []\n for (const [tid, orgs] of byTenant.entries()) {\n const hierarchy = computeHierarchyForOrganizations(orgs, tid)\n hierarchies.set(tid, hierarchy)\n for (const node of hierarchy.ordered) {\n orderedNodes.push({ tenantId: tid, node })\n }\n }\n\n let rows = orderedNodes\n if (query.status === 'active') {\n rows = rows.filter((entry) => entry.node.isActive)\n } else if (query.status === 'inactive') {\n rows = rows.filter((entry) => !entry.node.isActive)\n }\n\n if (search) {\n rows = rows.filter(({ node }) => {\n const pathLabel = (node.pathLabel || '').toLowerCase()\n return node.name.toLowerCase().includes(search) || pathLabel.includes(search)\n })\n }\n if (ids) {\n const idSet = new Set(ids)\n rows = rows.filter(({ node }) => idSet.has(node.id))\n }\n\n rows.sort((a, b) => {\n const nameA = tenantNameMap[a.tenantId] ?? a.tenantId\n const nameB = tenantNameMap[b.tenantId] ?? b.tenantId\n const tenantCompare = nameA.localeCompare(nameB)\n if (tenantCompare !== 0) return tenantCompare\n return a.node.pathLabel.localeCompare(b.node.pathLabel)\n })\n\n const total = rows.length\n const pageSize = query.pageSize\n const page = query.page\n const start = (page - 1) * pageSize\n const paged = rows.slice(start, start + pageSize)\n const recordIds: string[] = []\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const entry of paged) {\n const recordId = String(entry.node.id)\n recordIds.push(recordId)\n tenantIdByRecord[recordId] = entry.tenantId\n organizationIdByRecord[recordId] = recordId\n }\n const tenantFallbacks = Array.from(new Set(recordIds.map((id) => tenantIdByRecord[id]).filter((value): value is string => typeof value === 'string' && value.length > 0)))\n const cfByOrg = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.organization,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks,\n })\n : {}\n const items = paged.map(({ tenantId: tid, node }) => {\n const hierarchy = hierarchies.get(tid)\n const parentName = node.parentId && hierarchy ? hierarchy.map.get(node.parentId)?.name ?? null : null\n const pathLabel = node.pathLabel || node.name\n const recordId = String(node.id)\n return {\n id: node.id,\n name: node.name,\n slug: slugByOrgId.get(recordId) ?? null,\n tenantId: tid,\n tenantName: tenantNameMap[tid] ?? tid,\n parentId: node.parentId,\n parentName,\n depth: node.depth,\n rootId: node.rootId,\n treePath: node.treePath,\n pathLabel,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n childrenCount: node.childIds.length,\n descendantsCount: node.descendantIds.length,\n isActive: node.isActive,\n ...(cfByOrg[recordId] ?? {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId: null,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total, page, pageSize, totalPages, isSuperAdmin })\n }\n\n if (!tenantId) {\n return NextResponse.json({ items: [], error: 'Tenant scope required' }, { status: 400 })\n }\n\n const orgListFilter: FilterQuery<Organization> = { tenant: tenantId, deletedAt: null }\n const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: 'ASC' } })\n const hierarchy = computeHierarchyForOrganizations(orgs, tenantId)\n const slugByOrgId = new Map<string, string | null>()\n for (const org of orgs) {\n slugByOrgId.set(String(org.id), org.slug ?? null)\n }\n\n // Manage view: paginated flat list for a single tenant\n const search = (query.search || '').trim().toLowerCase()\n let rows = hierarchy.ordered\n if (status === 'active') {\n rows = rows.filter((node) => node.isActive)\n } else if (status === 'inactive') {\n rows = rows.filter((node) => !node.isActive)\n }\n\n if (search) {\n rows = rows.filter((node) => {\n const pathLabel = (node.pathLabel || '').toLowerCase()\n return node.name.toLowerCase().includes(search) || pathLabel.includes(search)\n })\n }\n if (ids) {\n const idSet = new Set(ids)\n rows = rows.filter((node) => idSet.has(node.id))\n }\n\n const total = rows.length\n const pageSize = query.pageSize\n const page = query.page\n const start = (page - 1) * pageSize\n const paged = rows.slice(start, start + pageSize)\n const recordIds: string[] = []\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const node of paged) {\n const recordId = String(node.id)\n recordIds.push(recordId)\n tenantIdByRecord[recordId] = node.tenantId ? String(node.tenantId) : null\n organizationIdByRecord[recordId] = recordId\n }\n const cfByOrg = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.organization,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks: tenantId ? [tenantId] : [],\n })\n : {}\n let tenantName: string | null = null\n if (isSuperAdmin && tenantId) {\n const tenant = await em.findOne(Tenant, { id: tenantId })\n if (tenant) {\n const display = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : stringId(tenant.id)\n tenantName = display\n }\n }\n const items = paged.map((node) => {\n const parentName = node.parentId ? hierarchy.map.get(node.parentId)?.name ?? null : null\n const pathLabel = node.pathLabel || node.name\n const recordId = String(node.id)\n return {\n id: node.id,\n name: node.name,\n slug: slugByOrgId.get(recordId) ?? null,\n tenantId: node.tenantId,\n tenantName,\n parentId: node.parentId,\n parentName,\n depth: node.depth,\n rootId: node.rootId,\n treePath: node.treePath,\n pathLabel,\n ancestorIds: node.ancestorIds,\n childIds: node.childIds,\n descendantIds: node.descendantIds,\n childrenCount: node.childIds.length,\n descendantsCount: node.descendantIds.length,\n isActive: node.isActive,\n ...(cfByOrg[recordId] ?? {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.organization',\n organizationId: null,\n tenantId,\n query,\n accessType: ids && ids.length === 1 ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total, page, pageSize, totalPages, isSuperAdmin })\n}\n\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst organizationCreateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst organizationDeleteRequestSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst organizationsGetDoc: OpenApiMethodDoc = {\n summary: 'List organizations',\n description: 'Returns organizations using options, tree, or paginated manage view depending on the `view` parameter.',\n tags: [directoryTag],\n query: viewSchema,\n responses: [\n { status: 200, description: 'Organization data for the requested view.', schema: organizationListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query or tenant scope', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsPostDoc: OpenApiMethodDoc = {\n summary: 'Create organization',\n description: 'Creates a new organization within a tenant and optionally assigns hierarchy relationships.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationCreateSchema,\n description: 'Organization attributes and optional hierarchy configuration.',\n },\n responses: [\n { status: 201, description: 'Organization created.', schema: organizationCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsPutDoc: OpenApiMethodDoc = {\n summary: 'Update organization',\n description: 'Updates organization details and hierarchy assignments.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationUpdateSchema,\n description: 'Organization identifier followed by fields to update.',\n },\n responses: [\n { status: 200, description: 'Organization updated.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst organizationsDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete organization',\n description: 'Soft deletes an organization identified by id.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: organizationDeleteRequestSchema,\n description: 'Identifier of the organization to delete.',\n },\n responses: [\n { status: 200, description: 'Organization deleted.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.organizations.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: directoryTag,\n summary: 'Manage organizations',\n methods: {\n GET: organizationsGetDoc,\n POST: organizationsPostDoc,\n PUT: organizationsPutDoc,\n DELETE: organizationsDeleteDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["orgs", "items", "orgListFilter", "hierarchy", "search", "slugByOrgId", "rows", "total", "pageSize", "page", "start", "paged", "recordIds", "tenantIdByRecord", "organizationIdByRecord", "cfByOrg", "totalPages"]
7
7
  }
@@ -99,6 +99,7 @@ function EditOrganizationPage({ params }) {
99
99
  setInitialValues({
100
100
  id: record.id,
101
101
  name: record.name,
102
+ slug: record.slug ?? "",
102
103
  parentId: record.parentId || "",
103
104
  isActive: record.isActive,
104
105
  tenantId: resolvedTenantId,
@@ -163,6 +164,12 @@ function EditOrganizationPage({ params }) {
163
164
  }
164
165
  ] : [],
165
166
  { id: "name", label: t("directory.organizations.form.field.name", "Name"), type: "text", required: true },
167
+ {
168
+ id: "slug",
169
+ label: t("directory.organizations.form.field.slug", "Slug"),
170
+ type: "text",
171
+ description: t("directory.organizations.form.field.slug.description", "URL-safe identifier used for the customer portal (lowercase letters, numbers, hyphens, underscores).")
172
+ },
166
173
  {
167
174
  id: "parentId",
168
175
  label: t("directory.organizations.form.field.parent", "Parent"),
@@ -196,7 +203,7 @@ function EditOrganizationPage({ params }) {
196
203
  },
197
204
  { id: "isActive", label: t("directory.organizations.form.field.isActive", "Active"), type: "checkbox" }
198
205
  ], [actorIsSuperAdmin, childSummary, parentTree, t, tenantId]);
199
- const detailFields = React.useMemo(() => actorIsSuperAdmin ? ["tenantId", "name", "parentId", "childrenInfo", "isActive"] : ["name", "parentId", "childrenInfo", "isActive"], [actorIsSuperAdmin]);
206
+ const detailFields = React.useMemo(() => actorIsSuperAdmin ? ["tenantId", "name", "slug", "parentId", "childrenInfo", "isActive"] : ["name", "slug", "parentId", "childrenInfo", "isActive"], [actorIsSuperAdmin]);
200
207
  const groups = React.useMemo(() => [
201
208
  { id: "details", title: t("directory.organizations.form.group.details", "Details"), column: 1, fields: detailFields },
202
209
  { id: "custom", title: t("directory.organizations.form.group.customFields", "Custom Data"), column: 2, kind: "customFields" }
@@ -216,7 +223,7 @@ function EditOrganizationPage({ params }) {
216
223
  fields,
217
224
  groups,
218
225
  entityId: E.directory.organization,
219
- initialValues: initialValues ?? { id: orgId, tenantId: tenantId ?? null, name: "", parentId: "", isActive: true, childIds: [] },
226
+ initialValues: initialValues ?? { id: orgId, tenantId: tenantId ?? null, name: "", slug: "", parentId: "", isActive: true, childIds: [] },
220
227
  isLoading: loading,
221
228
  loadingMessage: t("directory.organizations.form.loading", "Loading organization..."),
222
229
  submitLabel: t("directory.organizations.form.action.save", "Save"),
@@ -269,6 +276,10 @@ async function submitUpdateOrganization(options) {
269
276
  parentId: typeof values.parentId === "string" && values.parentId.length ? values.parentId : null,
270
277
  childIds: originalChildIds
271
278
  };
279
+ if (typeof values.slug === "string") {
280
+ const trimmedSlug = values.slug.trim();
281
+ payload.slug = trimmedSlug.length ? trimmedSlug : null;
282
+ }
272
283
  if (submittedTenantId !== void 0 && submittedTenantId !== null) {
273
284
  payload.tenantId = submittedTenantId;
274
285
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/directory/backend/directory/organizations/%5Bid%5D/edit/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { E } from '#generated/entities.ids.generated'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport {\n buildOrganizationTreeOptions,\n type OrganizationTreeNode,\n type OrganizationTreeOption,\n} from '@open-mercato/core/modules/directory/lib/tree'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\n\ntype TreeResponse = {\n items: OrganizationTreeNode[]\n}\n\ntype OrganizationResponse = {\n items: Array<{\n id: string\n name: string\n tenantId: string\n tenantName?: string | null\n parentId: string | null\n childIds: string[]\n ancestorIds: string[]\n descendantIds: string[]\n isActive: boolean\n pathLabel: string\n } & Record<string, unknown>>\n}\n\nconst TREE_STEP = 16\nconst TREE_PADDING = 12\n\nexport default function EditOrganizationPage({ params }: { params?: { id?: string } }) {\n const orgId = params?.id\n const t = useT()\n const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)\n const [pathLabel, setPathLabel] = React.useState<string>('')\n const [tenantId, setTenantId] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [parentTree, setParentTree] = React.useState<OrganizationTreeNode[]>([])\n const [childSummary, setChildSummary] = React.useState<OrganizationTreeOption[]>([])\n const [originalChildIds, setOriginalChildIds] = React.useState<string[]>([])\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const skipTenantEffectRef = React.useRef(true)\n\n React.useEffect(() => {\n let cancelled = false\n async function loadActor() {\n try {\n const { ok, result } = await apiCall<{ isSuperAdmin?: boolean }>('/api/auth/roles?page=1&pageSize=1')\n if (!cancelled && ok) setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n } catch {\n if (!cancelled) setActorIsSuperAdmin(false)\n }\n }\n loadActor()\n return () => { cancelled = true }\n }, [])\n\n const markSelectable = React.useCallback((nodes: OrganizationTreeNode[], excluded: Set<string>): OrganizationTreeNode[] => (\n nodes.map((node) => ({\n ...node,\n selectable: !excluded.has(node.id),\n children: Array.isArray(node.children) ? markSelectable(node.children, excluded) : [],\n }))\n ), [])\n\n const loadParentTree = React.useCallback(async (\n targetTenantId: string | null,\n excludedIds: Iterable<string>,\n ): Promise<OrganizationTreeNode[]> => {\n const treeParams = new URLSearchParams({ view: 'tree', includeInactive: 'true' })\n if (targetTenantId) treeParams.set('tenantId', targetTenantId)\n if (orgId) treeParams.set('ids', orgId)\n try {\n const { ok, result } = await apiCall<TreeResponse>(`/api/directory/organizations?${treeParams.toString()}`)\n if (!ok) {\n setParentTree([])\n return []\n }\n const baseTree = Array.isArray(result?.items) ? result.items ?? [] : []\n const excludedSet = new Set<string>(excludedIds)\n if (orgId) excludedSet.add(orgId)\n setParentTree(markSelectable(baseTree, excludedSet))\n return baseTree\n } catch {\n setParentTree([])\n return []\n }\n }, [markSelectable, orgId])\n\n React.useEffect(() => {\n if (!orgId) return\n const currentOrgId = orgId\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n const { ok, result } = await apiCall<OrganizationResponse>(\n `/api/directory/organizations?view=manage&ids=${currentOrgId}&status=all&includeInactive=true&page=1&pageSize=1`,\n )\n if (!ok) throw new Error(t('directory.organizations.form.errors.load', 'Failed to load organization'))\n const record = Array.isArray(result?.items) ? result.items?.[0] : undefined\n if (!record) throw new Error(t('directory.organizations.form.errors.notFound', 'Organization not found'))\n const resolvedTenantId = record.tenantId || null\n setTenantId(resolvedTenantId)\n const baseTree = await loadParentTree(resolvedTenantId, record.descendantIds ?? [])\n const fullTree = buildOrganizationTreeOptions(baseTree)\n const nodeMap = new Map(fullTree.map((opt) => [opt.value, opt]))\n const childrenDetails = record.childIds\n .map((id) => nodeMap.get(id))\n .filter((node): node is OrganizationTreeOption => !!node)\n setChildSummary(childrenDetails)\n setOriginalChildIds(Array.isArray(record.childIds) ? record.childIds : [])\n\n const customValues: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record as Record<string, unknown>)) {\n if (key.startsWith('cf_')) customValues[key] = value\n else if (key.startsWith('cf:')) customValues[`cf_${key.slice(3)}`] = value\n }\n setInitialValues({\n id: record.id,\n name: record.name,\n parentId: record.parentId || '',\n isActive: record.isActive,\n tenantId: resolvedTenantId,\n ...customValues,\n })\n setPathLabel(record.pathLabel)\n } catch (err: unknown) {\n if (!cancelled) {\n const fallback = t('directory.organizations.form.errors.load', 'Failed to load organization')\n const message = err instanceof Error && err.message ? err.message : fallback\n setError(message)\n }\n } finally {\n if (!cancelled) {\n skipTenantEffectRef.current = false\n setLoading(false)\n }\n }\n }\n load()\n return () => { cancelled = true }\n }, [loadParentTree, orgId, t])\n\n React.useEffect(() => {\n if (!actorIsSuperAdmin) return\n if (skipTenantEffectRef.current) {\n skipTenantEffectRef.current = false\n return\n }\n if (!orgId) return\n if (!tenantId) {\n setParentTree([])\n setChildSummary([])\n setOriginalChildIds([])\n return\n }\n void loadParentTree(tenantId, [])\n setChildSummary([])\n setOriginalChildIds([])\n }, [actorIsSuperAdmin, loadParentTree, orgId, tenantId])\n\n const fields = React.useMemo<CrudField[]>(() => [\n ...(actorIsSuperAdmin ? [\n {\n id: 'tenantId',\n label: t('directory.organizations.form.field.tenant', 'Tenant'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <TenantSelect\n id=\"tenantId\"\n value={typeof value === 'string' ? value : tenantId}\n onChange={(next) => {\n const normalized = next ?? null\n setTenantId(normalized)\n setValue(normalized)\n }}\n includeEmptyOption={false}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n } as CrudField,\n ] : []),\n { id: 'name', label: t('directory.organizations.form.field.name', 'Name'), type: 'text', required: true },\n {\n id: 'parentId',\n label: t('directory.organizations.form.field.parent', 'Parent'),\n type: 'custom',\n component: ({ id, value, setValue }) => (\n <OrganizationSelect\n id={id}\n value={value ? String(value) : null}\n onChange={(next) => setValue(next ?? '')}\n nodes={parentTree}\n includeEmptyOption\n emptyOptionLabel={t('directory.organizations.form.rootOption', '\u2014 Root level \u2014')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n },\n {\n id: 'childrenInfo',\n label: t('directory.organizations.form.field.children', 'Children'),\n type: 'custom',\n component: () => {\n if (!childSummary.length) {\n return <p className=\"text-xs text-muted-foreground\">{t('directory.organizations.form.children.empty', 'No direct children assigned.')}</p>\n }\n return (\n <ul className=\"space-y-1 text-sm\">\n {childSummary.map((child) => (\n <li key={child.value} className=\"leading-none\">\n <span style={{ paddingLeft: child.depth > 0 ? TREE_PADDING + (child.depth - 1) * TREE_STEP : 0 }}>\n {child.depth > 0 ? <span className=\"text-muted-foreground\">\u21B3 </span> : null}\n {child.name}\n </span>\n </li>\n ))}\n </ul>\n )\n },\n },\n { id: 'isActive', label: t('directory.organizations.form.field.isActive', 'Active'), type: 'checkbox' },\n ], [actorIsSuperAdmin, childSummary, parentTree, t, tenantId])\n\n const detailFields = React.useMemo(() => (\n actorIsSuperAdmin\n ? ['tenantId', 'name', 'parentId', 'childrenInfo', 'isActive']\n : ['name', 'parentId', 'childrenInfo', 'isActive']\n ), [actorIsSuperAdmin])\n\n const groups: CrudFormGroup[] = React.useMemo(() => ([\n { id: 'details', title: t('directory.organizations.form.group.details', 'Details'), column: 1, fields: detailFields },\n { id: 'custom', title: t('directory.organizations.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n ]), [detailFields, t])\n\n if (!orgId) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {t('directory.organizations.form.errors.missingId', 'Organization identifier is missing.')}\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (error && !loading && !initialValues) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">{error}</div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('directory.organizations.form.title.edit', 'Edit Organization')}\n backHref=\"/backend/directory/organizations\"\n versionHistory={{ resourceKind: 'directory.organization', resourceId: orgId ? String(orgId) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.directory.organization}\n initialValues={initialValues ?? { id: orgId, tenantId: tenantId ?? null, name: '', parentId: '', isActive: true, childIds: [] }}\n isLoading={loading}\n loadingMessage={t('directory.organizations.form.loading', 'Loading organization...')}\n submitLabel={t('directory.organizations.form.action.save', 'Save')}\n cancelHref=\"/backend/directory/organizations\"\n successRedirect={`/backend/directory/organizations?flash=${encodeURIComponent(t('directory.organizations.flash.updated', 'Organization updated'))}&type=success`}\n extraActions={pathLabel ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('directory.organizations.form.pathLabel', { path: pathLabel })}\n </span>\n ) : null}\n onSubmit={async (values) => {\n await submitUpdateOrganization({\n values: values as Record<string, unknown>,\n orgId: orgId ?? '',\n tenantId,\n originalChildIds,\n messages: {\n orgIdRequired: t('directory.organizations.errors.orgIdRequired', 'Organization identifier is required'),\n },\n })\n }}\n onDelete={async () => {\n await deleteCrud('directory/organizations', orgId ?? '', {\n errorMessage: t('directory.organizations.errors.deleteFailed', 'Failed to delete organization'),\n })\n }}\n deleteRedirect={`/backend/directory/organizations?flash=${encodeURIComponent(t('directory.organizations.flash.deleted', 'Organization deleted'))}&type=success`}\n />\n </PageBody>\n </Page>\n )\n}\n\ntype UpdateOrganizationPayload = {\n id: string\n name: string\n isActive: boolean\n parentId: string | null\n childIds: string[]\n tenantId?: string | null\n customFields?: Record<string, unknown>\n}\n\ntype UpdateOrganizationRequest = (payload: UpdateOrganizationPayload) => Promise<void>\n\nconst defaultUpdateOrganizationRequest: UpdateOrganizationRequest = async (payload) => {\n await updateCrud('directory/organizations', payload)\n}\n\nexport async function submitUpdateOrganization(options: {\n values: Record<string, unknown>\n orgId: string\n tenantId: string | null\n originalChildIds: string[]\n updateOrganization?: UpdateOrganizationRequest\n messages?: {\n orgIdRequired?: string\n }\n}): Promise<void> {\n const {\n values,\n orgId,\n tenantId,\n originalChildIds,\n updateOrganization = defaultUpdateOrganizationRequest,\n messages,\n } = options\n\n const payloadId = typeof values.id === 'string' && values.id.length ? values.id : orgId\n if (!payloadId) {\n const message = messages?.orgIdRequired ?? 'Organization identifier is required'\n throw createCrudFormError(message, { id: message })\n }\n\n const customFields = collectCustomFieldValues(values)\n\n const submittedTenantId =\n typeof values.tenantId === 'string' && values.tenantId.trim().length\n ? values.tenantId.trim()\n : tenantId\n\n const payload: UpdateOrganizationPayload = {\n id: payloadId,\n name: typeof values.name === 'string' ? values.name : '',\n isActive: values.isActive !== false,\n parentId:\n typeof values.parentId === 'string' && values.parentId.length\n ? values.parentId\n : null,\n childIds: originalChildIds,\n }\n\n if (submittedTenantId !== undefined && submittedTenantId !== null) {\n payload.tenantId = submittedTenantId\n }\n if (Object.keys(customFields).length > 0) {\n payload.customFields = customFields\n }\n\n await updateOrganization(payload)\n}\n"],
5
- "mappings": ";AAqLU,cA2CM,YA3CN;AApLV,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,OAGK;AACP,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAoD;AAC7D,SAAS,eAAe;AACxB,SAAS,YAAY,kBAAkB;AACvC,SAAS,gCAAgC;AACzC,SAAS,2BAA2B;AAqBpC,MAAM,YAAY;AAClB,MAAM,eAAe;AAEN,SAAR,qBAAsC,EAAE,OAAO,GAAiC;AACrF,QAAM,QAAQ,QAAQ;AACtB,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAyC,IAAI;AAC7F,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAiB,EAAE;AAC3D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAwB,IAAI;AAClE,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAiC,CAAC,CAAC;AAC7E,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAmC,CAAC,CAAC;AACnF,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAmB,CAAC,CAAC;AAC3E,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,KAAK;AACtE,QAAM,sBAAsB,MAAM,OAAO,IAAI;AAE7C,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,mBAAe,YAAY;AACzB,UAAI;AACF,cAAM,EAAE,IAAI,OAAO,IAAI,MAAM,QAAoC,mCAAmC;AACpG,YAAI,CAAC,aAAa,GAAI,sBAAqB,QAAQ,QAAQ,YAAY,CAAC;AAAA,MAC1E,QAAQ;AACN,YAAI,CAAC,UAAW,sBAAqB,KAAK;AAAA,MAC5C;AAAA,IACF;AACA,cAAU;AACV,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAK;AAAA,EAClC,GAAG,CAAC,CAAC;AAEL,QAAM,iBAAiB,MAAM,YAAY,CAAC,OAA+B,aACvE,MAAM,IAAI,CAAC,UAAU;AAAA,IACnB,GAAG;AAAA,IACH,YAAY,CAAC,SAAS,IAAI,KAAK,EAAE;AAAA,IACjC,UAAU,MAAM,QAAQ,KAAK,QAAQ,IAAI,eAAe,KAAK,UAAU,QAAQ,IAAI,CAAC;AAAA,EACtF,EAAE,GACD,CAAC,CAAC;AAEL,QAAM,iBAAiB,MAAM,YAAY,OACvC,gBACA,gBACoC;AACpC,UAAM,aAAa,IAAI,gBAAgB,EAAE,MAAM,QAAQ,iBAAiB,OAAO,CAAC;AAChF,QAAI,eAAgB,YAAW,IAAI,YAAY,cAAc;AAC7D,QAAI,MAAO,YAAW,IAAI,OAAO,KAAK;AACtC,QAAI;AACF,YAAM,EAAE,IAAI,OAAO,IAAI,MAAM,QAAsB,gCAAgC,WAAW,SAAS,CAAC,EAAE;AAC1G,UAAI,CAAC,IAAI;AACP,sBAAc,CAAC,CAAC;AAChB,eAAO,CAAC;AAAA,MACV;AACA,YAAM,WAAW,MAAM,QAAQ,QAAQ,KAAK,IAAI,OAAO,SAAS,CAAC,IAAI,CAAC;AACtE,YAAM,cAAc,IAAI,IAAY,WAAW;AAC/C,UAAI,MAAO,aAAY,IAAI,KAAK;AAChC,oBAAc,eAAe,UAAU,WAAW,CAAC;AACnD,aAAO;AAAA,IACT,QAAQ;AACN,oBAAc,CAAC,CAAC;AAChB,aAAO,CAAC;AAAA,IACV;AAAA,EACF,GAAG,CAAC,gBAAgB,KAAK,CAAC;AAE1B,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,MAAO;AACZ,UAAM,eAAe;AACrB,QAAI,YAAY;AAChB,mBAAe,OAAO;AACpB,iBAAW,IAAI;AACf,eAAS,IAAI;AACb,UAAI;AACF,cAAM,EAAE,IAAI,OAAO,IAAI,MAAM;AAAA,UAC3B,gDAAgD,YAAY;AAAA,QAC9D;AACA,YAAI,CAAC,GAAI,OAAM,IAAI,MAAM,EAAE,4CAA4C,6BAA6B,CAAC;AACrG,cAAM,SAAS,MAAM,QAAQ,QAAQ,KAAK,IAAI,OAAO,QAAQ,CAAC,IAAI;AAClE,YAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,EAAE,gDAAgD,wBAAwB,CAAC;AACxG,cAAM,mBAAmB,OAAO,YAAY;AAC5C,oBAAY,gBAAgB;AAC5B,cAAM,WAAW,MAAM,eAAe,kBAAkB,OAAO,iBAAiB,CAAC,CAAC;AAClF,cAAM,WAAW,6BAA6B,QAAQ;AACtD,cAAM,UAAU,IAAI,IAAI,SAAS,IAAI,CAAC,QAAQ,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC;AAC/D,cAAM,kBAAkB,OAAO,SAC5B,IAAI,CAAC,OAAO,QAAQ,IAAI,EAAE,CAAC,EAC3B,OAAO,CAAC,SAAyC,CAAC,CAAC,IAAI;AAC1D,wBAAgB,eAAe;AAC/B,4BAAoB,MAAM,QAAQ,OAAO,QAAQ,IAAI,OAAO,WAAW,CAAC,CAAC;AAEzE,cAAM,eAAwC,CAAC;AAC/C,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAiC,GAAG;AAC5E,cAAI,IAAI,WAAW,KAAK,EAAG,cAAa,GAAG,IAAI;AAAA,mBACtC,IAAI,WAAW,KAAK,EAAG,cAAa,MAAM,IAAI,MAAM,CAAC,CAAC,EAAE,IAAI;AAAA,QACvE;AACA,yBAAiB;AAAA,UACf,IAAI,OAAO;AAAA,UACX,MAAM,OAAO;AAAA,UACb,UAAU,OAAO,YAAY;AAAA,UAC7B,UAAU,OAAO;AAAA,UACjB,UAAU;AAAA,UACV,GAAG;AAAA,QACL,CAAC;AACD,qBAAa,OAAO,SAAS;AAAA,MAC/B,SAAS,KAAc;AACrB,YAAI,CAAC,WAAW;AACd,gBAAM,WAAW,EAAE,4CAA4C,6BAA6B;AAC5F,gBAAM,UAAU,eAAe,SAAS,IAAI,UAAU,IAAI,UAAU;AACpE,mBAAS,OAAO;AAAA,QAClB;AAAA,MACF,UAAE;AACA,YAAI,CAAC,WAAW;AACd,8BAAoB,UAAU;AAC9B,qBAAW,KAAK;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AACA,SAAK;AACL,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAK;AAAA,EAClC,GAAG,CAAC,gBAAgB,OAAO,CAAC,CAAC;AAE7B,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,oBAAoB,SAAS;AAC/B,0BAAoB,UAAU;AAC9B;AAAA,IACF;AACA,QAAI,CAAC,MAAO;AACZ,QAAI,CAAC,UAAU;AACb,oBAAc,CAAC,CAAC;AAChB,sBAAgB,CAAC,CAAC;AAClB,0BAAoB,CAAC,CAAC;AACtB;AAAA,IACF;AACA,SAAK,eAAe,UAAU,CAAC,CAAC;AAChC,oBAAgB,CAAC,CAAC;AAClB,wBAAoB,CAAC,CAAC;AAAA,EACxB,GAAG,CAAC,mBAAmB,gBAAgB,OAAO,QAAQ,CAAC;AAEvD,QAAM,SAAS,MAAM,QAAqB,MAAM;AAAA,IAC9C,GAAI,oBAAoB;AAAA,MACtB;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,6CAA6C,QAAQ;AAAA,QAC9D,MAAM;AAAA,QACN,WAAW,CAAC,EAAE,OAAO,SAAS,MAC5B;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,YAC3C,UAAU,CAAC,SAAS;AAClB,oBAAM,aAAa,QAAQ;AAC3B,0BAAY,UAAU;AACtB,uBAAS,UAAU;AAAA,YACrB;AAAA,YACA,oBAAoB;AAAA,YACpB,WAAU;AAAA;AAAA,QACZ;AAAA,MAEJ;AAAA,IACF,IAAI,CAAC;AAAA,IACL,EAAE,IAAI,QAAQ,OAAO,EAAE,2CAA2C,MAAM,GAAG,MAAM,QAAQ,UAAU,KAAK;AAAA,IACxG;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,6CAA6C,QAAQ;AAAA,MAC9D,MAAM;AAAA,MACN,WAAW,CAAC,EAAE,IAAI,OAAO,SAAS,MAChC;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,OAAO,QAAQ,OAAO,KAAK,IAAI;AAAA,UAC/B,UAAU,CAAC,SAAS,SAAS,QAAQ,EAAE;AAAA,UACvC,OAAO;AAAA,UACP,oBAAkB;AAAA,UAClB,kBAAkB,EAAE,2CAA2C,0BAAgB;AAAA,UAC/E,WAAU;AAAA;AAAA,MACZ;AAAA,IAEJ;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,+CAA+C,UAAU;AAAA,MAClE,MAAM;AAAA,MACN,WAAW,MAAM;AACf,YAAI,CAAC,aAAa,QAAQ;AACxB,iBAAO,oBAAC,OAAE,WAAU,iCAAiC,YAAE,+CAA+C,8BAA8B,GAAE;AAAA,QACxI;AACA,eACE,oBAAC,QAAG,WAAU,qBACX,uBAAa,IAAI,CAAC,UACjB,oBAAC,QAAqB,WAAU,gBAC9B,+BAAC,UAAK,OAAO,EAAE,aAAa,MAAM,QAAQ,IAAI,gBAAgB,MAAM,QAAQ,KAAK,YAAY,EAAE,GAC5F;AAAA,gBAAM,QAAQ,IAAI,oBAAC,UAAK,WAAU,yBAAwB,qBAAE,IAAU;AAAA,UACtE,MAAM;AAAA,WACT,KAJO,MAAM,KAKf,CACD,GACH;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,EAAE,IAAI,YAAY,OAAO,EAAE,+CAA+C,QAAQ,GAAG,MAAM,WAAW;AAAA,EACxG,GAAG,CAAC,mBAAmB,cAAc,YAAY,GAAG,QAAQ,CAAC;AAE7D,QAAM,eAAe,MAAM,QAAQ,MACjC,oBACI,CAAC,YAAY,QAAQ,YAAY,gBAAgB,UAAU,IAC3D,CAAC,QAAQ,YAAY,gBAAgB,UAAU,GAClD,CAAC,iBAAiB,CAAC;AAEtB,QAAM,SAA0B,MAAM,QAAQ,MAAO;AAAA,IACnD,EAAE,IAAI,WAAW,OAAO,EAAE,8CAA8C,SAAS,GAAG,QAAQ,GAAG,QAAQ,aAAa;AAAA,IACpH,EAAE,IAAI,UAAU,OAAO,EAAE,mDAAmD,aAAa,GAAG,QAAQ,GAAG,MAAM,eAAe;AAAA,EAC9H,GAAI,CAAC,cAAc,CAAC,CAAC;AAErB,MAAI,CAAC,OAAO;AACV,WACE,oBAAC,QACC,8BAAC,YACC,8BAAC,SAAI,WAAU,6FACZ,YAAE,iDAAiD,qCAAqC,GAC3F,GACF,GACF;AAAA,EAEJ;AAEA,MAAI,SAAS,CAAC,WAAW,CAAC,eAAe;AACvC,WACE,oBAAC,QACC,8BAAC,YACC,8BAAC,SAAI,WAAU,6FAA6F,iBAAM,GACpH,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,QACC,8BAAC,YACC;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,2CAA2C,mBAAmB;AAAA,MACvE,UAAS;AAAA,MACT,gBAAgB,EAAE,cAAc,0BAA0B,YAAY,QAAQ,OAAO,KAAK,IAAI,GAAG;AAAA,MACjG;AAAA,MACA;AAAA,MACA,UAAU,EAAE,UAAU;AAAA,MACtB,eAAe,iBAAiB,EAAE,IAAI,OAAO,UAAU,YAAY,MAAM,MAAM,IAAI,UAAU,IAAI,UAAU,MAAM,UAAU,CAAC,EAAE;AAAA,MAC9H,WAAW;AAAA,MACX,gBAAgB,EAAE,wCAAwC,yBAAyB;AAAA,MACnF,aAAa,EAAE,4CAA4C,MAAM;AAAA,MACjE,YAAW;AAAA,MACX,iBAAiB,0CAA0C,mBAAmB,EAAE,yCAAyC,sBAAsB,CAAC,CAAC;AAAA,MACjJ,cAAc,YACZ,oBAAC,UAAK,WAAU,iCACb,YAAE,0CAA0C,EAAE,MAAM,UAAU,CAAC,GAClE,IACE;AAAA,MACJ,UAAU,OAAO,WAAW;AAC1B,cAAM,yBAAyB;AAAA,UAC7B;AAAA,UACA,OAAO,SAAS;AAAA,UAChB;AAAA,UACA;AAAA,UACA,UAAU;AAAA,YACR,eAAe,EAAE,gDAAgD,qCAAqC;AAAA,UACxG;AAAA,QACF,CAAC;AAAA,MACH;AAAA,MACA,UAAU,YAAY;AACpB,cAAM,WAAW,2BAA2B,SAAS,IAAI;AAAA,UACvD,cAAc,EAAE,+CAA+C,+BAA+B;AAAA,QAChG,CAAC;AAAA,MACH;AAAA,MACA,gBAAgB,0CAA0C,mBAAmB,EAAE,yCAAyC,sBAAsB,CAAC,CAAC;AAAA;AAAA,EAClJ,GACF,GACF;AAEJ;AAcA,MAAM,mCAA8D,OAAO,YAAY;AACrF,QAAM,WAAW,2BAA2B,OAAO;AACrD;AAEA,eAAsB,yBAAyB,SAS7B;AAChB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,qBAAqB;AAAA,IACrB;AAAA,EACF,IAAI;AAEJ,QAAM,YAAY,OAAO,OAAO,OAAO,YAAY,OAAO,GAAG,SAAS,OAAO,KAAK;AAClF,MAAI,CAAC,WAAW;AACd,UAAM,UAAU,UAAU,iBAAiB;AAC3C,UAAM,oBAAoB,SAAS,EAAE,IAAI,QAAQ,CAAC;AAAA,EACpD;AAEA,QAAM,eAAe,yBAAyB,MAAM;AAEpD,QAAM,oBACJ,OAAO,OAAO,aAAa,YAAY,OAAO,SAAS,KAAK,EAAE,SAC1D,OAAO,SAAS,KAAK,IACrB;AAEN,QAAM,UAAqC;AAAA,IACzC,IAAI;AAAA,IACJ,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;AAAA,IACtD,UAAU,OAAO,aAAa;AAAA,IAC9B,UACE,OAAO,OAAO,aAAa,YAAY,OAAO,SAAS,SACnD,OAAO,WACP;AAAA,IACN,UAAU;AAAA,EACZ;AAEA,MAAI,sBAAsB,UAAa,sBAAsB,MAAM;AACjE,YAAQ,WAAW;AAAA,EACrB;AACA,MAAI,OAAO,KAAK,YAAY,EAAE,SAAS,GAAG;AACxC,YAAQ,eAAe;AAAA,EACzB;AAEA,QAAM,mBAAmB,OAAO;AAClC;",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { E } from '#generated/entities.ids.generated'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport {\n buildOrganizationTreeOptions,\n type OrganizationTreeNode,\n type OrganizationTreeOption,\n} from '@open-mercato/core/modules/directory/lib/tree'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\n\ntype TreeResponse = {\n items: OrganizationTreeNode[]\n}\n\ntype OrganizationResponse = {\n items: Array<{\n id: string\n name: string\n slug?: string | null\n tenantId: string\n tenantName?: string | null\n parentId: string | null\n childIds: string[]\n ancestorIds: string[]\n descendantIds: string[]\n isActive: boolean\n pathLabel: string\n } & Record<string, unknown>>\n}\n\nconst TREE_STEP = 16\nconst TREE_PADDING = 12\n\nexport default function EditOrganizationPage({ params }: { params?: { id?: string } }) {\n const orgId = params?.id\n const t = useT()\n const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)\n const [pathLabel, setPathLabel] = React.useState<string>('')\n const [tenantId, setTenantId] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [parentTree, setParentTree] = React.useState<OrganizationTreeNode[]>([])\n const [childSummary, setChildSummary] = React.useState<OrganizationTreeOption[]>([])\n const [originalChildIds, setOriginalChildIds] = React.useState<string[]>([])\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const skipTenantEffectRef = React.useRef(true)\n\n React.useEffect(() => {\n let cancelled = false\n async function loadActor() {\n try {\n const { ok, result } = await apiCall<{ isSuperAdmin?: boolean }>('/api/auth/roles?page=1&pageSize=1')\n if (!cancelled && ok) setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n } catch {\n if (!cancelled) setActorIsSuperAdmin(false)\n }\n }\n loadActor()\n return () => { cancelled = true }\n }, [])\n\n const markSelectable = React.useCallback((nodes: OrganizationTreeNode[], excluded: Set<string>): OrganizationTreeNode[] => (\n nodes.map((node) => ({\n ...node,\n selectable: !excluded.has(node.id),\n children: Array.isArray(node.children) ? markSelectable(node.children, excluded) : [],\n }))\n ), [])\n\n const loadParentTree = React.useCallback(async (\n targetTenantId: string | null,\n excludedIds: Iterable<string>,\n ): Promise<OrganizationTreeNode[]> => {\n const treeParams = new URLSearchParams({ view: 'tree', includeInactive: 'true' })\n if (targetTenantId) treeParams.set('tenantId', targetTenantId)\n if (orgId) treeParams.set('ids', orgId)\n try {\n const { ok, result } = await apiCall<TreeResponse>(`/api/directory/organizations?${treeParams.toString()}`)\n if (!ok) {\n setParentTree([])\n return []\n }\n const baseTree = Array.isArray(result?.items) ? result.items ?? [] : []\n const excludedSet = new Set<string>(excludedIds)\n if (orgId) excludedSet.add(orgId)\n setParentTree(markSelectable(baseTree, excludedSet))\n return baseTree\n } catch {\n setParentTree([])\n return []\n }\n }, [markSelectable, orgId])\n\n React.useEffect(() => {\n if (!orgId) return\n const currentOrgId = orgId\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n const { ok, result } = await apiCall<OrganizationResponse>(\n `/api/directory/organizations?view=manage&ids=${currentOrgId}&status=all&includeInactive=true&page=1&pageSize=1`,\n )\n if (!ok) throw new Error(t('directory.organizations.form.errors.load', 'Failed to load organization'))\n const record = Array.isArray(result?.items) ? result.items?.[0] : undefined\n if (!record) throw new Error(t('directory.organizations.form.errors.notFound', 'Organization not found'))\n const resolvedTenantId = record.tenantId || null\n setTenantId(resolvedTenantId)\n const baseTree = await loadParentTree(resolvedTenantId, record.descendantIds ?? [])\n const fullTree = buildOrganizationTreeOptions(baseTree)\n const nodeMap = new Map(fullTree.map((opt) => [opt.value, opt]))\n const childrenDetails = record.childIds\n .map((id) => nodeMap.get(id))\n .filter((node): node is OrganizationTreeOption => !!node)\n setChildSummary(childrenDetails)\n setOriginalChildIds(Array.isArray(record.childIds) ? record.childIds : [])\n\n const customValues: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record as Record<string, unknown>)) {\n if (key.startsWith('cf_')) customValues[key] = value\n else if (key.startsWith('cf:')) customValues[`cf_${key.slice(3)}`] = value\n }\n setInitialValues({\n id: record.id,\n name: record.name,\n slug: record.slug ?? '',\n parentId: record.parentId || '',\n isActive: record.isActive,\n tenantId: resolvedTenantId,\n ...customValues,\n })\n setPathLabel(record.pathLabel)\n } catch (err: unknown) {\n if (!cancelled) {\n const fallback = t('directory.organizations.form.errors.load', 'Failed to load organization')\n const message = err instanceof Error && err.message ? err.message : fallback\n setError(message)\n }\n } finally {\n if (!cancelled) {\n skipTenantEffectRef.current = false\n setLoading(false)\n }\n }\n }\n load()\n return () => { cancelled = true }\n }, [loadParentTree, orgId, t])\n\n React.useEffect(() => {\n if (!actorIsSuperAdmin) return\n if (skipTenantEffectRef.current) {\n skipTenantEffectRef.current = false\n return\n }\n if (!orgId) return\n if (!tenantId) {\n setParentTree([])\n setChildSummary([])\n setOriginalChildIds([])\n return\n }\n void loadParentTree(tenantId, [])\n setChildSummary([])\n setOriginalChildIds([])\n }, [actorIsSuperAdmin, loadParentTree, orgId, tenantId])\n\n const fields = React.useMemo<CrudField[]>(() => [\n ...(actorIsSuperAdmin ? [\n {\n id: 'tenantId',\n label: t('directory.organizations.form.field.tenant', 'Tenant'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <TenantSelect\n id=\"tenantId\"\n value={typeof value === 'string' ? value : tenantId}\n onChange={(next) => {\n const normalized = next ?? null\n setTenantId(normalized)\n setValue(normalized)\n }}\n includeEmptyOption={false}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n } as CrudField,\n ] : []),\n { id: 'name', label: t('directory.organizations.form.field.name', 'Name'), type: 'text', required: true },\n {\n id: 'slug',\n label: t('directory.organizations.form.field.slug', 'Slug'),\n type: 'text',\n description: t('directory.organizations.form.field.slug.description', 'URL-safe identifier used for the customer portal (lowercase letters, numbers, hyphens, underscores).'),\n },\n {\n id: 'parentId',\n label: t('directory.organizations.form.field.parent', 'Parent'),\n type: 'custom',\n component: ({ id, value, setValue }) => (\n <OrganizationSelect\n id={id}\n value={value ? String(value) : null}\n onChange={(next) => setValue(next ?? '')}\n nodes={parentTree}\n includeEmptyOption\n emptyOptionLabel={t('directory.organizations.form.rootOption', '\u2014 Root level \u2014')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n },\n {\n id: 'childrenInfo',\n label: t('directory.organizations.form.field.children', 'Children'),\n type: 'custom',\n component: () => {\n if (!childSummary.length) {\n return <p className=\"text-xs text-muted-foreground\">{t('directory.organizations.form.children.empty', 'No direct children assigned.')}</p>\n }\n return (\n <ul className=\"space-y-1 text-sm\">\n {childSummary.map((child) => (\n <li key={child.value} className=\"leading-none\">\n <span style={{ paddingLeft: child.depth > 0 ? TREE_PADDING + (child.depth - 1) * TREE_STEP : 0 }}>\n {child.depth > 0 ? <span className=\"text-muted-foreground\">\u21B3 </span> : null}\n {child.name}\n </span>\n </li>\n ))}\n </ul>\n )\n },\n },\n { id: 'isActive', label: t('directory.organizations.form.field.isActive', 'Active'), type: 'checkbox' },\n ], [actorIsSuperAdmin, childSummary, parentTree, t, tenantId])\n\n const detailFields = React.useMemo(() => (\n actorIsSuperAdmin\n ? ['tenantId', 'name', 'slug', 'parentId', 'childrenInfo', 'isActive']\n : ['name', 'slug', 'parentId', 'childrenInfo', 'isActive']\n ), [actorIsSuperAdmin])\n\n const groups: CrudFormGroup[] = React.useMemo(() => ([\n { id: 'details', title: t('directory.organizations.form.group.details', 'Details'), column: 1, fields: detailFields },\n { id: 'custom', title: t('directory.organizations.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n ]), [detailFields, t])\n\n if (!orgId) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {t('directory.organizations.form.errors.missingId', 'Organization identifier is missing.')}\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (error && !loading && !initialValues) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">{error}</div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('directory.organizations.form.title.edit', 'Edit Organization')}\n backHref=\"/backend/directory/organizations\"\n versionHistory={{ resourceKind: 'directory.organization', resourceId: orgId ? String(orgId) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.directory.organization}\n initialValues={initialValues ?? { id: orgId, tenantId: tenantId ?? null, name: '', slug: '', parentId: '', isActive: true, childIds: [] }}\n isLoading={loading}\n loadingMessage={t('directory.organizations.form.loading', 'Loading organization...')}\n submitLabel={t('directory.organizations.form.action.save', 'Save')}\n cancelHref=\"/backend/directory/organizations\"\n successRedirect={`/backend/directory/organizations?flash=${encodeURIComponent(t('directory.organizations.flash.updated', 'Organization updated'))}&type=success`}\n extraActions={pathLabel ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('directory.organizations.form.pathLabel', { path: pathLabel })}\n </span>\n ) : null}\n onSubmit={async (values) => {\n await submitUpdateOrganization({\n values: values as Record<string, unknown>,\n orgId: orgId ?? '',\n tenantId,\n originalChildIds,\n messages: {\n orgIdRequired: t('directory.organizations.errors.orgIdRequired', 'Organization identifier is required'),\n },\n })\n }}\n onDelete={async () => {\n await deleteCrud('directory/organizations', orgId ?? '', {\n errorMessage: t('directory.organizations.errors.deleteFailed', 'Failed to delete organization'),\n })\n }}\n deleteRedirect={`/backend/directory/organizations?flash=${encodeURIComponent(t('directory.organizations.flash.deleted', 'Organization deleted'))}&type=success`}\n />\n </PageBody>\n </Page>\n )\n}\n\ntype UpdateOrganizationPayload = {\n id: string\n name: string\n slug?: string | null\n isActive: boolean\n parentId: string | null\n childIds: string[]\n tenantId?: string | null\n customFields?: Record<string, unknown>\n}\n\ntype UpdateOrganizationRequest = (payload: UpdateOrganizationPayload) => Promise<void>\n\nconst defaultUpdateOrganizationRequest: UpdateOrganizationRequest = async (payload) => {\n await updateCrud('directory/organizations', payload)\n}\n\nexport async function submitUpdateOrganization(options: {\n values: Record<string, unknown>\n orgId: string\n tenantId: string | null\n originalChildIds: string[]\n updateOrganization?: UpdateOrganizationRequest\n messages?: {\n orgIdRequired?: string\n }\n}): Promise<void> {\n const {\n values,\n orgId,\n tenantId,\n originalChildIds,\n updateOrganization = defaultUpdateOrganizationRequest,\n messages,\n } = options\n\n const payloadId = typeof values.id === 'string' && values.id.length ? values.id : orgId\n if (!payloadId) {\n const message = messages?.orgIdRequired ?? 'Organization identifier is required'\n throw createCrudFormError(message, { id: message })\n }\n\n const customFields = collectCustomFieldValues(values)\n\n const submittedTenantId =\n typeof values.tenantId === 'string' && values.tenantId.trim().length\n ? values.tenantId.trim()\n : tenantId\n\n const payload: UpdateOrganizationPayload = {\n id: payloadId,\n name: typeof values.name === 'string' ? values.name : '',\n isActive: values.isActive !== false,\n parentId:\n typeof values.parentId === 'string' && values.parentId.length\n ? values.parentId\n : null,\n childIds: originalChildIds,\n }\n\n if (typeof values.slug === 'string') {\n const trimmedSlug = values.slug.trim()\n payload.slug = trimmedSlug.length ? trimmedSlug : null\n }\n\n if (submittedTenantId !== undefined && submittedTenantId !== null) {\n payload.tenantId = submittedTenantId\n }\n if (Object.keys(customFields).length > 0) {\n payload.customFields = customFields\n }\n\n await updateOrganization(payload)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -103,6 +103,12 @@ function CreateOrganizationPage() {
103
103
  }
104
104
  ] : [],
105
105
  { id: "name", label: t("directory.organizations.form.field.name", "Name"), type: "text", required: true },
106
+ {
107
+ id: "slug",
108
+ label: t("directory.organizations.form.field.slug", "Slug"),
109
+ type: "text",
110
+ description: t("directory.organizations.form.field.slug.description", "URL-safe identifier used for the customer portal (lowercase letters, numbers, hyphens, underscores). Generated from the name when left blank.")
111
+ },
106
112
  {
107
113
  id: "parentId",
108
114
  label: t("directory.organizations.form.field.parent", "Parent"),
@@ -136,7 +142,7 @@ function CreateOrganizationPage() {
136
142
  },
137
143
  { id: "isActive", label: t("directory.organizations.form.field.isActive", "Active"), type: "checkbox" }
138
144
  ], [actorIsSuperAdmin, selectedTenantId, t, tree]);
139
- const detailFields = React.useMemo(() => actorIsSuperAdmin ? ["tenantId", "name", "parentId", "childIds", "isActive"] : ["name", "parentId", "childIds", "isActive"], [actorIsSuperAdmin]);
145
+ const detailFields = React.useMemo(() => actorIsSuperAdmin ? ["tenantId", "name", "slug", "parentId", "childIds", "isActive"] : ["name", "slug", "parentId", "childIds", "isActive"], [actorIsSuperAdmin]);
140
146
  const groups = React.useMemo(() => [
141
147
  { id: "details", title: t("directory.organizations.form.group.details", "Details"), column: 1, fields: detailFields },
142
148
  { id: "custom", title: t("directory.organizations.form.group.customFields", "Custom Data"), column: 2, kind: "customFields" }
@@ -151,7 +157,7 @@ function CreateOrganizationPage() {
151
157
  fields,
152
158
  groups,
153
159
  entityId: E.directory.organization,
154
- initialValues: { tenantId: selectedTenantId ?? null, name: "", parentId: "", childIds: [], isActive: true },
160
+ initialValues: { tenantId: selectedTenantId ?? null, name: "", slug: "", parentId: "", childIds: [], isActive: true },
155
161
  submitLabel: t("directory.organizations.form.action.create", "Create"),
156
162
  cancelHref: "/backend/directory/organizations",
157
163
  successRedirect: `/backend/directory/organizations?flash=${successMessage}&type=success`,
@@ -193,6 +199,10 @@ async function submitCreateOrganization(options) {
193
199
  parentId: typeof values.parentId === "string" && values.parentId.length ? values.parentId : null,
194
200
  childIds: Array.isArray(values.childIds) ? values.childIds.filter((id) => typeof id === "string") : []
195
201
  };
202
+ if (typeof values.slug === "string") {
203
+ const trimmedSlug = values.slug.trim();
204
+ if (trimmedSlug.length) payload.slug = trimmedSlug;
205
+ }
196
206
  if (tenantValue) payload.tenantId = tenantValue;
197
207
  if (Object.keys(customFields).length > 0) payload.customFields = customFields;
198
208
  await createOrganization(payload);