@open-mercato/core 0.6.6-develop.6454.1.0647656f77 → 0.6.6-develop.6457.1.41b39a465a

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.
@@ -1,4 +1,4 @@
1
- [build:core] found 3457 entry points
1
+ [build:core] found 3458 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 186 entry points
4
4
  [build:core:generated] built successfully
@@ -3,7 +3,7 @@ const widget = {
3
3
  metadata: {
4
4
  id: "customer_accounts.injection.account-status",
5
5
  title: "Customer Account Status",
6
- description: "Shows customer portal account status on CRM person detail",
6
+ description: "customer_accounts.widgets.accountStatus.description",
7
7
  features: ["customer_accounts.view"],
8
8
  priority: 100,
9
9
  enabled: true
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customer_accounts/widgets/injection/account-status/widget.ts"],
4
- "sourcesContent": ["import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'\nimport AccountStatusWidget from './widget.client'\n\nconst widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {\n metadata: {\n id: 'customer_accounts.injection.account-status',\n title: 'Customer Account Status',\n description: 'Shows customer portal account status on CRM person detail',\n features: ['customer_accounts.view'],\n priority: 100,\n enabled: true,\n },\n Widget: AccountStatusWidget,\n}\n\nexport default widget\n"],
4
+ "sourcesContent": ["import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'\nimport AccountStatusWidget from './widget.client'\n\nconst widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {\n metadata: {\n id: 'customer_accounts.injection.account-status',\n title: 'Customer Account Status',\n description: 'customer_accounts.widgets.accountStatus.description',\n features: ['customer_accounts.view'],\n priority: 100,\n enabled: true,\n },\n Widget: AccountStatusWidget,\n}\n\nexport default widget\n"],
5
5
  "mappings": "AACA,OAAO,yBAAyB;AAEhC,MAAM,SAAkF;AAAA,EACtF,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,wBAAwB;AAAA,IACnC,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AAAA,EACA,QAAQ;AACV;AAEA,IAAO,iBAAQ;",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  import { invalidateCrudCache } from "@open-mercato/shared/lib/crud/cache";
2
2
  import { runWithCacheTenant } from "@open-mercato/cache";
3
3
  import { createModuleQueue } from "@open-mercato/queue";
4
+ import "../commands/deals.js";
4
5
  const CUSTOMERS_DEALS_BULK_UPDATE_STAGE_QUEUE = "customers-deals-bulk-update-stage";
5
6
  const CUSTOMERS_DEALS_BULK_UPDATE_OWNER_QUEUE = "customers-deals-bulk-update-owner";
6
7
  const queues = /* @__PURE__ */ new Map();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/lib/bulkDeals.ts"],
4
- "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager as CoreEntityManager } from '@mikro-orm/core'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { createModuleQueue, type Queue } from '@open-mercato/queue'\nimport type { ProgressService, ProgressServiceContext } from '../../progress/lib/progressService'\n\nexport const CUSTOMERS_DEALS_BULK_UPDATE_STAGE_QUEUE = 'customers-deals-bulk-update-stage'\nexport const CUSTOMERS_DEALS_BULK_UPDATE_OWNER_QUEUE = 'customers-deals-bulk-update-owner'\n\nconst queues = new Map<string, Queue<Record<string, unknown>>>()\n\nexport function getCustomersQueue(queueName: string): Queue<Record<string, unknown>> {\n const existing = queues.get(queueName)\n if (existing) return existing\n const concurrency = Math.max(\n 1,\n Number.parseInt(process.env.CUSTOMERS_QUEUE_CONCURRENCY ?? '3', 10) || 3,\n )\n const created = createModuleQueue<Record<string, unknown>>(queueName, { concurrency })\n queues.set(queueName, created)\n return created\n}\n\nexport type CustomersDealsBulkScope = {\n organizationId: string\n tenantId: string\n userId?: string | null\n}\n\nexport type CustomersDealsBulkUpdateStageJobPayload = {\n progressJobId: string\n ids: string[]\n pipelineStageId: string\n scope: CustomersDealsBulkScope\n}\n\nexport type CustomersDealsBulkUpdateOwnerJobPayload = {\n progressJobId: string\n ids: string[]\n ownerUserId: string | null\n scope: CustomersDealsBulkScope\n}\n\nexport type CustomersDealsBulkFailedItem = {\n id: string\n message: string\n}\n\nexport type CustomersDealsBulkSummary = {\n affectedCount: number\n failedCount: number\n failedItems: CustomersDealsBulkFailedItem[]\n}\n\nexport class BulkDealsPreflightError extends Error {\n readonly code: string\n constructor(code: string, message: string) {\n super(message)\n this.name = 'BulkDealsPreflightError'\n this.code = code\n }\n}\n\nasync function verifyPipelineStageExists(\n em: CoreEntityManager,\n stageId: string,\n scope: CustomersDealsBulkScope,\n): Promise<void> {\n // The `pipelineStageId` posted from the kanban is a `customer_pipeline_stages.id`\n // (the per-pipeline stage definition that `commands/deals.ts:loadPipelineStageSnapshot`\n // also reads). It is NOT a `customer_dictionary_entries.id` \u2014 those entries are a\n // per-tenant dictionary used to centralize stage colours/icons by normalized label.\n const rows = await em.getConnection().execute<Array<{ id: string }>>(\n `SELECT id FROM customer_pipeline_stages\n WHERE id = ?\n AND tenant_id = ?\n AND organization_id = ?\n LIMIT 1`,\n [stageId, scope.tenantId, scope.organizationId],\n )\n if (rows.length === 0) {\n throw new BulkDealsPreflightError(\n 'pipeline_stage_not_found',\n `Pipeline stage ${stageId} does not exist in this tenant`,\n )\n }\n}\n\nconst BULK_CACHE_ALIASES = ['customers.deals']\n\nfunction buildCommandContext(\n scope: CustomersDealsBulkScope,\n container: AwilixContainer,\n): CommandRuntimeContext {\n return {\n container,\n auth: null,\n organizationScope: {\n selectedId: scope.organizationId,\n filterIds: [scope.organizationId],\n allowedIds: [scope.organizationId],\n tenantId: scope.tenantId,\n },\n selectedOrganizationId: scope.organizationId,\n organizationIds: [scope.organizationId],\n }\n}\n\nasync function runBulkDealUpdate(params: {\n container: AwilixContainer\n progressJobId: string\n ids: string[]\n scope: CustomersDealsBulkScope\n cacheSource: string\n buildBody: (id: string) => Record<string, unknown>\n logTag: string\n}): Promise<CustomersDealsBulkSummary> {\n const { container, progressJobId, ids, scope, cacheSource, buildBody, logTag } = params\n const commandBus = container.resolve('commandBus') as CommandBus\n const progressService = container.resolve('progressService') as ProgressService\n const progressContext: ProgressServiceContext = {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n }\n\n await progressService.startJob(progressJobId, progressContext)\n await progressService.updateProgress(\n progressJobId,\n { totalCount: ids.length, processedCount: 0 },\n progressContext,\n )\n\n const commandContext = buildCommandContext(scope, container)\n const updatedIds = new Set<string>()\n const failedItems: CustomersDealsBulkFailedItem[] = []\n let affectedCount = 0\n\n for (const [index, id] of ids.entries()) {\n try {\n // `customers.deals.update` parses its input with `parseWithCustomFields(dealUpdateSchema, rawInput)`\n // and reads `parsed.id` at the top level (see commands/deals.ts). Wrapping the body in\n // `{ body: ... }` made `id` undefined and every per-deal call ZodError'd in the bulk\n // worker, so the job completed with affectedCount=0 in CI (TC-CRM-068/069).\n await commandBus.execute<Record<string, unknown>, { dealId: string }>(\n 'customers.deals.update',\n {\n input: buildBody(id),\n ctx: commandContext,\n skipCacheInvalidation: true,\n },\n )\n affectedCount += 1\n updatedIds.add(id)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n failedItems.push({ id, message })\n console.warn(`[${logTag}] failed to update deal`, { jobId: progressJobId, id, error })\n }\n\n await progressService.updateProgress(\n progressJobId,\n { totalCount: ids.length, processedCount: index + 1 },\n progressContext,\n )\n }\n\n await runWithCacheTenant(scope.tenantId, async () => {\n for (const id of updatedIds) {\n await invalidateCrudCache(\n container,\n 'customers.deal',\n { id, organizationId: scope.organizationId, tenantId: scope.tenantId },\n scope.tenantId,\n cacheSource,\n BULK_CACHE_ALIASES,\n )\n }\n })\n\n const summary: CustomersDealsBulkSummary = {\n affectedCount,\n failedCount: failedItems.length,\n failedItems,\n }\n await progressService.completeJob(progressJobId, { resultSummary: summary }, progressContext)\n return summary\n}\n\nexport async function bulkUpdateDealStageWithProgress(params: {\n container: AwilixContainer\n progressJobId: string\n ids: string[]\n pipelineStageId: string\n scope: CustomersDealsBulkScope\n}): Promise<CustomersDealsBulkSummary> {\n // Pre-flight check: verify the target stage exists in the caller's tenant scope before\n // doing N per-deal command calls. An invalid stage would otherwise produce N identical\n // failures in `runBulkDealUpdate` and a noisy `failedItems` list.\n const em = params.container.resolve('em') as CoreEntityManager\n await verifyPipelineStageExists(em, params.pipelineStageId, params.scope)\n return runBulkDealUpdate({\n container: params.container,\n progressJobId: params.progressJobId,\n ids: params.ids,\n scope: params.scope,\n cacheSource: 'bulk-update-stage:customers.deals',\n logTag: 'customers.deals.bulk-update-stage',\n buildBody: (id) => ({ id, pipelineStageId: params.pipelineStageId }),\n })\n}\n\nexport async function bulkUpdateDealOwnerWithProgress(params: {\n container: AwilixContainer\n progressJobId: string\n ids: string[]\n ownerUserId: string | null\n scope: CustomersDealsBulkScope\n}): Promise<CustomersDealsBulkSummary> {\n return runBulkDealUpdate({\n container: params.container,\n progressJobId: params.progressJobId,\n ids: params.ids,\n scope: params.scope,\n cacheSource: 'bulk-update-owner:customers.deals',\n logTag: 'customers.deals.bulk-update-owner',\n buildBody: (id) => ({ id, ownerUserId: params.ownerUserId }),\n })\n}\n"],
5
- "mappings": "AAGA,SAAS,2BAA2B;AACpC,SAAS,0BAA0B;AACnC,SAAS,yBAAqC;AAGvC,MAAM,0CAA0C;AAChD,MAAM,0CAA0C;AAEvD,MAAM,SAAS,oBAAI,IAA4C;AAExD,SAAS,kBAAkB,WAAmD;AACnF,QAAM,WAAW,OAAO,IAAI,SAAS;AACrC,MAAI,SAAU,QAAO;AACrB,QAAM,cAAc,KAAK;AAAA,IACvB;AAAA,IACA,OAAO,SAAS,QAAQ,IAAI,+BAA+B,KAAK,EAAE,KAAK;AAAA,EACzE;AACA,QAAM,UAAU,kBAA2C,WAAW,EAAE,YAAY,CAAC;AACrF,SAAO,IAAI,WAAW,OAAO;AAC7B,SAAO;AACT;AAiCO,MAAM,gCAAgC,MAAM;AAAA,EAEjD,YAAY,MAAc,SAAiB;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAEA,eAAe,0BACb,IACA,SACA,OACe;AAKf,QAAM,OAAO,MAAM,GAAG,cAAc,EAAE;AAAA,IACpC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,CAAC,SAAS,MAAM,UAAU,MAAM,cAAc;AAAA,EAChD;AACA,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,kBAAkB,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,MAAM,qBAAqB,CAAC,iBAAiB;AAE7C,SAAS,oBACP,OACA,WACuB;AACvB,SAAO;AAAA,IACL;AAAA,IACA,MAAM;AAAA,IACN,mBAAmB;AAAA,MACjB,YAAY,MAAM;AAAA,MAClB,WAAW,CAAC,MAAM,cAAc;AAAA,MAChC,YAAY,CAAC,MAAM,cAAc;AAAA,MACjC,UAAU,MAAM;AAAA,IAClB;AAAA,IACA,wBAAwB,MAAM;AAAA,IAC9B,iBAAiB,CAAC,MAAM,cAAc;AAAA,EACxC;AACF;AAEA,eAAe,kBAAkB,QAQM;AACrC,QAAM,EAAE,WAAW,eAAe,KAAK,OAAO,aAAa,WAAW,OAAO,IAAI;AACjF,QAAM,aAAa,UAAU,QAAQ,YAAY;AACjD,QAAM,kBAAkB,UAAU,QAAQ,iBAAiB;AAC3D,QAAM,kBAA0C;AAAA,IAC9C,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,QAAQ,MAAM;AAAA,EAChB;AAEA,QAAM,gBAAgB,SAAS,eAAe,eAAe;AAC7D,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA,EAAE,YAAY,IAAI,QAAQ,gBAAgB,EAAE;AAAA,IAC5C;AAAA,EACF;AAEA,QAAM,iBAAiB,oBAAoB,OAAO,SAAS;AAC3D,QAAM,aAAa,oBAAI,IAAY;AACnC,QAAM,cAA8C,CAAC;AACrD,MAAI,gBAAgB;AAEpB,aAAW,CAAC,OAAO,EAAE,KAAK,IAAI,QAAQ,GAAG;AACvC,QAAI;AAKF,YAAM,WAAW;AAAA,QACf;AAAA,QACA;AAAA,UACE,OAAO,UAAU,EAAE;AAAA,UACnB,KAAK;AAAA,UACL,uBAAuB;AAAA,QACzB;AAAA,MACF;AACA,uBAAiB;AACjB,iBAAW,IAAI,EAAE;AAAA,IACnB,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,kBAAY,KAAK,EAAE,IAAI,QAAQ,CAAC;AAChC,cAAQ,KAAK,IAAI,MAAM,2BAA2B,EAAE,OAAO,eAAe,IAAI,MAAM,CAAC;AAAA,IACvF;AAEA,UAAM,gBAAgB;AAAA,MACpB;AAAA,MACA,EAAE,YAAY,IAAI,QAAQ,gBAAgB,QAAQ,EAAE;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,mBAAmB,MAAM,UAAU,YAAY;AACnD,eAAW,MAAM,YAAY;AAC3B,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA,EAAE,IAAI,gBAAgB,MAAM,gBAAgB,UAAU,MAAM,SAAS;AAAA,QACrE,MAAM;AAAA,QACN;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,UAAqC;AAAA,IACzC;AAAA,IACA,aAAa,YAAY;AAAA,IACzB;AAAA,EACF;AACA,QAAM,gBAAgB,YAAY,eAAe,EAAE,eAAe,QAAQ,GAAG,eAAe;AAC5F,SAAO;AACT;AAEA,eAAsB,gCAAgC,QAMf;AAIrC,QAAM,KAAK,OAAO,UAAU,QAAQ,IAAI;AACxC,QAAM,0BAA0B,IAAI,OAAO,iBAAiB,OAAO,KAAK;AACxE,SAAO,kBAAkB;AAAA,IACvB,WAAW,OAAO;AAAA,IAClB,eAAe,OAAO;AAAA,IACtB,KAAK,OAAO;AAAA,IACZ,OAAO,OAAO;AAAA,IACd,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,WAAW,CAAC,QAAQ,EAAE,IAAI,iBAAiB,OAAO,gBAAgB;AAAA,EACpE,CAAC;AACH;AAEA,eAAsB,gCAAgC,QAMf;AACrC,SAAO,kBAAkB;AAAA,IACvB,WAAW,OAAO;AAAA,IAClB,eAAe,OAAO;AAAA,IACtB,KAAK,OAAO;AAAA,IACZ,OAAO,OAAO;AAAA,IACd,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,WAAW,CAAC,QAAQ,EAAE,IAAI,aAAa,OAAO,YAAY;AAAA,EAC5D,CAAC;AACH;",
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager as CoreEntityManager } from '@mikro-orm/core'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { createModuleQueue, type Queue } from '@open-mercato/queue'\nimport type { ProgressService, ProgressServiceContext } from '../../progress/lib/progressService'\n// Eagerly register the deal command handlers into this module graph's command\n// registry. The bulk workers dispatch `customers.deals.update` through the command\n// bus, but command handlers became lazy-loaded (#3703): they are only reachable if\n// their generated loader was registered into the SAME `@open-mercato/shared` instance\n// the worker's bus reads. A queue worker runs in its own container/process and must\n// not depend on that external lazy registration having reached its instance \u2014 in the\n// standalone integration harness the loader registry and the worker's bus can resolve\n// to different `@open-mercato/shared` instances, leaving the lazy loader unreachable\n// (issue: bulk deal jobs fail with \"Command handler not registered for id\n// customers.deals.update\"). Importing the command module here registers the handlers\n// through the worker's own import graph, so the dispatch always resolves.\nimport '../commands/deals'\n\nexport const CUSTOMERS_DEALS_BULK_UPDATE_STAGE_QUEUE = 'customers-deals-bulk-update-stage'\nexport const CUSTOMERS_DEALS_BULK_UPDATE_OWNER_QUEUE = 'customers-deals-bulk-update-owner'\n\nconst queues = new Map<string, Queue<Record<string, unknown>>>()\n\nexport function getCustomersQueue(queueName: string): Queue<Record<string, unknown>> {\n const existing = queues.get(queueName)\n if (existing) return existing\n const concurrency = Math.max(\n 1,\n Number.parseInt(process.env.CUSTOMERS_QUEUE_CONCURRENCY ?? '3', 10) || 3,\n )\n const created = createModuleQueue<Record<string, unknown>>(queueName, { concurrency })\n queues.set(queueName, created)\n return created\n}\n\nexport type CustomersDealsBulkScope = {\n organizationId: string\n tenantId: string\n userId?: string | null\n}\n\nexport type CustomersDealsBulkUpdateStageJobPayload = {\n progressJobId: string\n ids: string[]\n pipelineStageId: string\n scope: CustomersDealsBulkScope\n}\n\nexport type CustomersDealsBulkUpdateOwnerJobPayload = {\n progressJobId: string\n ids: string[]\n ownerUserId: string | null\n scope: CustomersDealsBulkScope\n}\n\nexport type CustomersDealsBulkFailedItem = {\n id: string\n message: string\n}\n\nexport type CustomersDealsBulkSummary = {\n affectedCount: number\n failedCount: number\n failedItems: CustomersDealsBulkFailedItem[]\n}\n\nexport class BulkDealsPreflightError extends Error {\n readonly code: string\n constructor(code: string, message: string) {\n super(message)\n this.name = 'BulkDealsPreflightError'\n this.code = code\n }\n}\n\nasync function verifyPipelineStageExists(\n em: CoreEntityManager,\n stageId: string,\n scope: CustomersDealsBulkScope,\n): Promise<void> {\n // The `pipelineStageId` posted from the kanban is a `customer_pipeline_stages.id`\n // (the per-pipeline stage definition that `commands/deals.ts:loadPipelineStageSnapshot`\n // also reads). It is NOT a `customer_dictionary_entries.id` \u2014 those entries are a\n // per-tenant dictionary used to centralize stage colours/icons by normalized label.\n const rows = await em.getConnection().execute<Array<{ id: string }>>(\n `SELECT id FROM customer_pipeline_stages\n WHERE id = ?\n AND tenant_id = ?\n AND organization_id = ?\n LIMIT 1`,\n [stageId, scope.tenantId, scope.organizationId],\n )\n if (rows.length === 0) {\n throw new BulkDealsPreflightError(\n 'pipeline_stage_not_found',\n `Pipeline stage ${stageId} does not exist in this tenant`,\n )\n }\n}\n\nconst BULK_CACHE_ALIASES = ['customers.deals']\n\nfunction buildCommandContext(\n scope: CustomersDealsBulkScope,\n container: AwilixContainer,\n): CommandRuntimeContext {\n return {\n container,\n auth: null,\n organizationScope: {\n selectedId: scope.organizationId,\n filterIds: [scope.organizationId],\n allowedIds: [scope.organizationId],\n tenantId: scope.tenantId,\n },\n selectedOrganizationId: scope.organizationId,\n organizationIds: [scope.organizationId],\n }\n}\n\nasync function runBulkDealUpdate(params: {\n container: AwilixContainer\n progressJobId: string\n ids: string[]\n scope: CustomersDealsBulkScope\n cacheSource: string\n buildBody: (id: string) => Record<string, unknown>\n logTag: string\n}): Promise<CustomersDealsBulkSummary> {\n const { container, progressJobId, ids, scope, cacheSource, buildBody, logTag } = params\n const commandBus = container.resolve('commandBus') as CommandBus\n const progressService = container.resolve('progressService') as ProgressService\n const progressContext: ProgressServiceContext = {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n }\n\n await progressService.startJob(progressJobId, progressContext)\n await progressService.updateProgress(\n progressJobId,\n { totalCount: ids.length, processedCount: 0 },\n progressContext,\n )\n\n const commandContext = buildCommandContext(scope, container)\n const updatedIds = new Set<string>()\n const failedItems: CustomersDealsBulkFailedItem[] = []\n let affectedCount = 0\n\n for (const [index, id] of ids.entries()) {\n try {\n // `customers.deals.update` parses its input with `parseWithCustomFields(dealUpdateSchema, rawInput)`\n // and reads `parsed.id` at the top level (see commands/deals.ts). Wrapping the body in\n // `{ body: ... }` made `id` undefined and every per-deal call ZodError'd in the bulk\n // worker, so the job completed with affectedCount=0 in CI (TC-CRM-068/069).\n await commandBus.execute<Record<string, unknown>, { dealId: string }>(\n 'customers.deals.update',\n {\n input: buildBody(id),\n ctx: commandContext,\n skipCacheInvalidation: true,\n },\n )\n affectedCount += 1\n updatedIds.add(id)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n failedItems.push({ id, message })\n console.warn(`[${logTag}] failed to update deal`, { jobId: progressJobId, id, error })\n }\n\n await progressService.updateProgress(\n progressJobId,\n { totalCount: ids.length, processedCount: index + 1 },\n progressContext,\n )\n }\n\n await runWithCacheTenant(scope.tenantId, async () => {\n for (const id of updatedIds) {\n await invalidateCrudCache(\n container,\n 'customers.deal',\n { id, organizationId: scope.organizationId, tenantId: scope.tenantId },\n scope.tenantId,\n cacheSource,\n BULK_CACHE_ALIASES,\n )\n }\n })\n\n const summary: CustomersDealsBulkSummary = {\n affectedCount,\n failedCount: failedItems.length,\n failedItems,\n }\n await progressService.completeJob(progressJobId, { resultSummary: summary }, progressContext)\n return summary\n}\n\nexport async function bulkUpdateDealStageWithProgress(params: {\n container: AwilixContainer\n progressJobId: string\n ids: string[]\n pipelineStageId: string\n scope: CustomersDealsBulkScope\n}): Promise<CustomersDealsBulkSummary> {\n // Pre-flight check: verify the target stage exists in the caller's tenant scope before\n // doing N per-deal command calls. An invalid stage would otherwise produce N identical\n // failures in `runBulkDealUpdate` and a noisy `failedItems` list.\n const em = params.container.resolve('em') as CoreEntityManager\n await verifyPipelineStageExists(em, params.pipelineStageId, params.scope)\n return runBulkDealUpdate({\n container: params.container,\n progressJobId: params.progressJobId,\n ids: params.ids,\n scope: params.scope,\n cacheSource: 'bulk-update-stage:customers.deals',\n logTag: 'customers.deals.bulk-update-stage',\n buildBody: (id) => ({ id, pipelineStageId: params.pipelineStageId }),\n })\n}\n\nexport async function bulkUpdateDealOwnerWithProgress(params: {\n container: AwilixContainer\n progressJobId: string\n ids: string[]\n ownerUserId: string | null\n scope: CustomersDealsBulkScope\n}): Promise<CustomersDealsBulkSummary> {\n return runBulkDealUpdate({\n container: params.container,\n progressJobId: params.progressJobId,\n ids: params.ids,\n scope: params.scope,\n cacheSource: 'bulk-update-owner:customers.deals',\n logTag: 'customers.deals.bulk-update-owner',\n buildBody: (id) => ({ id, ownerUserId: params.ownerUserId }),\n })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,23 +7,13 @@ import { RowActions } from "@open-mercato/ui/backend/RowActions";
7
7
  import { Button } from "@open-mercato/ui/primitives/button";
8
8
  import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
9
9
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
10
+ import { buildEntitiesCsv } from "../lib/entitiesCsvExport.js";
10
11
  const columns = [
11
12
  { accessorKey: "entityId", header: "Entity", meta: { priority: 1 }, cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "font-mono", children: String(getValue()) }) },
12
13
  { accessorKey: "label", header: "Label", meta: { priority: 2 } },
13
14
  { accessorKey: "source", header: "Source", meta: { priority: 3 } },
14
15
  { accessorKey: "count", header: "Fields", meta: { priority: 4 } }
15
16
  ];
16
- function toCsv(rows) {
17
- const header = ["entityId", "label", "source", "count"];
18
- const esc = (s) => {
19
- const str = String(s ?? "");
20
- if (/[",\n]/.test(str)) return '"' + str.replace(/"/g, '""') + '"';
21
- return str;
22
- };
23
- const lines = [header.join(",")];
24
- for (const r of rows) lines.push([r.entityId, r.label, r.source, r.count].map(esc).join(","));
25
- return lines.join("\n");
26
- }
27
17
  function SystemEntitiesTable() {
28
18
  const [sorting, setSorting] = React.useState([{ id: "entityId", desc: false }]);
29
19
  const [page, setPage] = React.useState(1);
@@ -49,7 +39,7 @@ function SystemEntitiesTable() {
49
39
  {
50
40
  title: "System Entities",
51
41
  actions: /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => {
52
- const csv = toCsv(rows);
42
+ const csv = buildEntitiesCsv(rows);
53
43
  const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
54
44
  const url = URL.createObjectURL(blob);
55
45
  const a = document.createElement("a");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/entities/components/SystemEntitiesTable.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\n\ntype EntityRow = {\n entityId: string\n label: string\n source: 'code' | 'custom'\n count: number\n}\n\ntype EntitiesResponse = { items: EntityRow[] }\n\nconst columns: ColumnDef<EntityRow>[] = [\n { accessorKey: 'entityId', header: 'Entity', meta: { priority: 1 }, cell: ({ getValue }) => <span className=\"font-mono\">{String(getValue())}</span> },\n { accessorKey: 'label', header: 'Label', meta: { priority: 2 } },\n { accessorKey: 'source', header: 'Source', meta: { priority: 3 } },\n { accessorKey: 'count', header: 'Fields', meta: { priority: 4 } },\n]\n\nfunction toCsv(rows: EntityRow[]) {\n const header = ['entityId','label','source','count']\n const esc = (s: string | number) => {\n const str = String(s ?? '')\n if (/[\",\\n]/.test(str)) return '\"' + str.replace(/\"/g, '\"\"') + '\"'\n return str\n }\n const lines = [header.join(',')]\n for (const r of rows) lines.push([r.entityId, r.label, r.source, r.count].map(esc).join(','))\n return lines.join('\\n')\n}\n\nexport default function SystemEntitiesTable() {\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'entityId', desc: false }])\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const scopeVersion = useOrganizationScopeVersion()\n\n const { data, isLoading } = useQuery<EntitiesResponse>({\n queryKey: ['custom-entities', scopeVersion],\n queryFn: async () => {\n return readApiResultOrThrow<EntitiesResponse>('/api/entities/entities', undefined, {\n errorMessage: 'Failed to load entities',\n })\n },\n })\n\n const rowsAll = data?.items || []\n // Filter to only show system entities (source: 'code')\n const systemRows = rowsAll.filter(row => row.source === 'code')\n const rows = React.useMemo(() => {\n if (!search) return systemRows\n const q = search.toLowerCase()\n return systemRows.filter(r => r.entityId.toLowerCase().includes(q) || r.label.toLowerCase().includes(q))\n }, [systemRows, search])\n\n return (\n <DataTable\n title=\"System Entities\"\n actions={(\n <>\n <Button variant=\"outline\" onClick={() => {\n const csv = toCsv(rows)\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = 'entities-system.csv'\n a.click()\n URL.revokeObjectURL(url)\n }}>Export</Button>\n </>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(v) => { setSearch(v); setPage(1) }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n perspective={{ tableId: 'entities.system.list' }}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: 'Edit', href: `/backend/entities/system/${encodeURIComponent(row.entityId)}` },\n ]}\n />\n )}\n pagination={{ page, pageSize: 50, total: rows.length, totalPages: 1, onPageChange: setPage }}\n isLoading={isLoading}\n />\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { buildEntitiesCsv } from '../lib/entitiesCsvExport'\n\ntype EntityRow = {\n entityId: string\n label: string\n source: 'code' | 'custom'\n count: number\n}\n\ntype EntitiesResponse = { items: EntityRow[] }\n\nconst columns: ColumnDef<EntityRow>[] = [\n { accessorKey: 'entityId', header: 'Entity', meta: { priority: 1 }, cell: ({ getValue }) => <span className=\"font-mono\">{String(getValue())}</span> },\n { accessorKey: 'label', header: 'Label', meta: { priority: 2 } },\n { accessorKey: 'source', header: 'Source', meta: { priority: 3 } },\n { accessorKey: 'count', header: 'Fields', meta: { priority: 4 } },\n]\n\nexport default function SystemEntitiesTable() {\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'entityId', desc: false }])\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const scopeVersion = useOrganizationScopeVersion()\n\n const { data, isLoading } = useQuery<EntitiesResponse>({\n queryKey: ['custom-entities', scopeVersion],\n queryFn: async () => {\n return readApiResultOrThrow<EntitiesResponse>('/api/entities/entities', undefined, {\n errorMessage: 'Failed to load entities',\n })\n },\n })\n\n const rowsAll = data?.items || []\n // Filter to only show system entities (source: 'code')\n const systemRows = rowsAll.filter(row => row.source === 'code')\n const rows = React.useMemo(() => {\n if (!search) return systemRows\n const q = search.toLowerCase()\n return systemRows.filter(r => r.entityId.toLowerCase().includes(q) || r.label.toLowerCase().includes(q))\n }, [systemRows, search])\n\n return (\n <DataTable\n title=\"System Entities\"\n actions={(\n <>\n <Button variant=\"outline\" onClick={() => {\n const csv = buildEntitiesCsv(rows)\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = 'entities-system.csv'\n a.click()\n URL.revokeObjectURL(url)\n }}>Export</Button>\n </>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(v) => { setSearch(v); setPage(1) }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n perspective={{ tableId: 'entities.system.list' }}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: 'Edit', href: `/backend/entities/system/${encodeURIComponent(row.entityId)}` },\n ]}\n />\n )}\n pagination={{ page, pageSize: 50, total: rows.length, totalPages: 1, onPageChange: setPage }}\n isLoading={isLoading}\n />\n )\n}\n"],
5
+ "mappings": ";AAqB8F,SAkCtF,UAlCsF;AApB9F,YAAY,WAAW;AACvB,SAAS,gBAAgB;AAEzB,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,4BAA4B;AACrC,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AAWjC,MAAM,UAAkC;AAAA,EACtC,EAAE,aAAa,YAAY,QAAQ,UAAU,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC,EAAE,SAAS,MAAM,oBAAC,UAAK,WAAU,aAAa,iBAAO,SAAS,CAAC,GAAE,EAAQ;AAAA,EACpJ,EAAE,aAAa,SAAS,QAAQ,SAAS,MAAM,EAAE,UAAU,EAAE,EAAE;AAAA,EAC/D,EAAE,aAAa,UAAU,QAAQ,UAAU,MAAM,EAAE,UAAU,EAAE,EAAE;AAAA,EACjE,EAAE,aAAa,SAAS,QAAQ,UAAU,MAAM,EAAE,UAAU,EAAE,EAAE;AAClE;AAEe,SAAR,sBAAuC;AAC5C,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAuB,CAAC,EAAE,IAAI,YAAY,MAAM,MAAM,CAAC,CAAC;AAC5F,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,eAAe,4BAA4B;AAEjD,QAAM,EAAE,MAAM,UAAU,IAAI,SAA2B;AAAA,IACrD,UAAU,CAAC,mBAAmB,YAAY;AAAA,IAC1C,SAAS,YAAY;AACnB,aAAO,qBAAuC,0BAA0B,QAAW;AAAA,QACjF,cAAc;AAAA,MAChB,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,QAAM,UAAU,MAAM,SAAS,CAAC;AAEhC,QAAM,aAAa,QAAQ,OAAO,SAAO,IAAI,WAAW,MAAM;AAC9D,QAAM,OAAO,MAAM,QAAQ,MAAM;AAC/B,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,IAAI,OAAO,YAAY;AAC7B,WAAO,WAAW,OAAO,OAAK,EAAE,SAAS,YAAY,EAAE,SAAS,CAAC,KAAK,EAAE,MAAM,YAAY,EAAE,SAAS,CAAC,CAAC;AAAA,EACzG,GAAG,CAAC,YAAY,MAAM,CAAC;AAEvB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,SACE,gCACE,8BAAC,UAAO,SAAQ,WAAU,SAAS,MAAM;AACvC,cAAM,MAAM,iBAAiB,IAAI;AACjC,cAAM,OAAO,IAAI,KAAK,CAAC,GAAG,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAChE,cAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,cAAM,IAAI,SAAS,cAAc,GAAG;AACpC,UAAE,OAAO;AACT,UAAE,WAAW;AACb,UAAE,MAAM;AACR,YAAI,gBAAgB,GAAG;AAAA,MACzB,GAAG,oBAAM,GACX;AAAA,MAEF;AAAA,MACA,MAAM;AAAA,MACN,aAAa;AAAA,MACb,gBAAgB,CAAC,MAAM;AAAE,kBAAU,CAAC;AAAG,gBAAQ,CAAC;AAAA,MAAE;AAAA,MAClD,UAAQ;AAAA,MACR;AAAA,MACA,iBAAiB;AAAA,MACjB,aAAa,EAAE,SAAS,uBAAuB;AAAA,MAC/C,YAAY,CAAC,QACX;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,YACL,EAAE,IAAI,QAAQ,OAAO,QAAQ,MAAM,4BAA4B,mBAAmB,IAAI,QAAQ,CAAC,GAAG;AAAA,UACpG;AAAA;AAAA,MACF;AAAA,MAEF,YAAY,EAAE,MAAM,UAAU,IAAI,OAAO,KAAK,QAAQ,YAAY,GAAG,cAAc,QAAQ;AAAA,MAC3F;AAAA;AAAA,EACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -8,6 +8,7 @@ import { ListEmptyState } from "@open-mercato/ui/backend/filters/ListEmptyState"
8
8
  import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
9
9
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
10
10
  import { useT } from "@open-mercato/shared/lib/i18n/context";
11
+ import { buildEntitiesCsv } from "../lib/entitiesCsvExport.js";
11
12
  function buildColumns(t) {
12
13
  return [
13
14
  { accessorKey: "entityId", header: t("entities.user.table.column.entity", "Entity"), meta: { priority: 1 }, cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "font-mono", children: String(getValue()) }) },
@@ -22,17 +23,6 @@ function buildColumns(t) {
22
23
  }
23
24
  ];
24
25
  }
25
- function toCsv(rows) {
26
- const header = ["entityId", "label", "source", "count", "showInSidebar"];
27
- const esc = (s) => {
28
- const str = String(s ?? "");
29
- if (/[",\n]/.test(str)) return '"' + str.replace(/"/g, '""') + '"';
30
- return str;
31
- };
32
- const lines = [header.join(",")];
33
- for (const r of rows) lines.push([r.entityId, r.label, r.source, r.count, r.showInSidebar || false].map(esc).join(","));
34
- return lines.join("\n");
35
- }
36
26
  function UserEntitiesTable() {
37
27
  const t = useT();
38
28
  const [sorting, setSorting] = React.useState([{ id: "entityId", desc: false }]);
@@ -61,7 +51,7 @@ function UserEntitiesTable() {
61
51
  title: t("entities.user.table.title", "User Entities"),
62
52
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [
63
53
  /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => {
64
- const csv = toCsv(rows);
54
+ const csv = buildEntitiesCsv(rows, { includeSidebar: true });
65
55
  const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
66
56
  const url = URL.createObjectURL(blob);
67
57
  const a = document.createElement("a");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/entities/components/UserEntitiesTable.tsx"],
4
- "sourcesContent": ["\n\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useQuery } from '@tanstack/react-query'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { DataTable, RowActions, Button } from '@open-mercato/ui'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype EntityRow = {\n entityId: string\n label: string\n source: 'code' | 'custom'\n count: number\n showInSidebar?: boolean\n}\n\ntype EntitiesResponse = { items: EntityRow[] }\n\nfunction buildColumns(t: (key: string, fallback: string) => string): ColumnDef<EntityRow>[] {\n return [\n { accessorKey: 'entityId', header: t('entities.user.table.column.entity', 'Entity'), meta: { priority: 1 }, cell: ({ getValue }) => <span className=\"font-mono\">{String(getValue())}</span> },\n { accessorKey: 'label', header: t('entities.user.table.column.label', 'Label'), meta: { priority: 2 } },\n { accessorKey: 'source', header: t('entities.user.table.column.source', 'Source'), meta: { priority: 3 } },\n { accessorKey: 'count', header: t('entities.user.table.column.fields', 'Fields'), meta: { priority: 4 } },\n { \n accessorKey: 'showInSidebar', \n header: t('entities.user.table.column.inSidebar', 'In Sidebar'), \n meta: { priority: 5 },\n cell: ({ getValue }) => (\n <span className={`px-2 py-1 rounded text-xs ${\n getValue() ? 'bg-status-success-bg text-status-success-text' : 'bg-muted text-muted-foreground'\n }`}>\n {getValue() ? t('common.yes', 'Yes') : t('common.no', 'No')}\n </span>\n )\n },\n ]\n}\n\nfunction toCsv(rows: EntityRow[]) {\n const header = ['entityId','label','source','count','showInSidebar']\n const esc = (s: string | number | boolean) => {\n const str = String(s ?? '')\n if (/[\",\\n]/.test(str)) return '\"' + str.replace(/\"/g, '\"\"') + '\"'\n return str\n }\n const lines = [header.join(',')]\n for (const r of rows) lines.push([r.entityId, r.label, r.source, r.count, r.showInSidebar || false].map(esc).join(','))\n return lines.join('\\n')\n}\n\nexport default function UserEntitiesTable() {\n const t = useT()\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'entityId', desc: false }])\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const scopeVersion = useOrganizationScopeVersion()\n \n const columns = React.useMemo(() => buildColumns(t), [t])\n\n const { data, isLoading } = useQuery<EntitiesResponse>({\n queryKey: ['custom-entities', scopeVersion],\n queryFn: async () => {\n return readApiResultOrThrow<EntitiesResponse>('/api/entities/entities', undefined, {\n errorMessage: 'Failed to load entities',\n })\n },\n })\n\n const rowsAll = data?.items || []\n // Filter to only show user entities (source: 'custom')\n const userRows = rowsAll.filter(row => row.source === 'custom')\n const rows = React.useMemo(() => {\n if (!search) return userRows\n const q = search.toLowerCase()\n return userRows.filter(r => r.entityId.toLowerCase().includes(q) || r.label.toLowerCase().includes(q))\n }, [userRows, search])\n\n return (\n <DataTable\n title={t('entities.user.table.title', 'User Entities')}\n actions={(\n <>\n <Button variant=\"outline\" onClick={() => {\n const csv = toCsv(rows)\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = 'entities-user.csv'\n a.click()\n URL.revokeObjectURL(url)\n }}>{t('common.export', 'Export')}</Button>\n <Button asChild>\n <Link href=\"/backend/entities/user/create\">{t('common.create', 'Create')}</Link>\n </Button>\n </>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(v) => { setSearch(v); setPage(1) }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n perspective={{ tableId: 'entities.user.list' }}\n emptyState={(\n <ListEmptyState\n entityName={t('entities.user.table.title', 'User Entities')}\n createHref=\"/backend/entities/user/create\"\n createLabel={t('common.create', 'Create')}\n />\n )}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: t('common.edit', 'Edit'), href: `/backend/entities/user/${encodeURIComponent(row.entityId)}` },\n { id: 'show-records', label: t('entities.user.table.actions.showRecords', 'Show records'), href: `/backend/entities/user/${encodeURIComponent(row.entityId)}/records` },\n ]}\n />\n )}\n pagination={{ page, pageSize: 50, total: rows.length, totalPages: 1, onPageChange: setPage }}\n isLoading={isLoading}\n />\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\n\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useQuery } from '@tanstack/react-query'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { DataTable, RowActions, Button } from '@open-mercato/ui'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { buildEntitiesCsv } from '../lib/entitiesCsvExport'\n\ntype EntityRow = {\n entityId: string\n label: string\n source: 'code' | 'custom'\n count: number\n showInSidebar?: boolean\n}\n\ntype EntitiesResponse = { items: EntityRow[] }\n\nfunction buildColumns(t: (key: string, fallback: string) => string): ColumnDef<EntityRow>[] {\n return [\n { accessorKey: 'entityId', header: t('entities.user.table.column.entity', 'Entity'), meta: { priority: 1 }, cell: ({ getValue }) => <span className=\"font-mono\">{String(getValue())}</span> },\n { accessorKey: 'label', header: t('entities.user.table.column.label', 'Label'), meta: { priority: 2 } },\n { accessorKey: 'source', header: t('entities.user.table.column.source', 'Source'), meta: { priority: 3 } },\n { accessorKey: 'count', header: t('entities.user.table.column.fields', 'Fields'), meta: { priority: 4 } },\n { \n accessorKey: 'showInSidebar', \n header: t('entities.user.table.column.inSidebar', 'In Sidebar'), \n meta: { priority: 5 },\n cell: ({ getValue }) => (\n <span className={`px-2 py-1 rounded text-xs ${\n getValue() ? 'bg-status-success-bg text-status-success-text' : 'bg-muted text-muted-foreground'\n }`}>\n {getValue() ? t('common.yes', 'Yes') : t('common.no', 'No')}\n </span>\n )\n },\n ]\n}\n\nexport default function UserEntitiesTable() {\n const t = useT()\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'entityId', desc: false }])\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const scopeVersion = useOrganizationScopeVersion()\n \n const columns = React.useMemo(() => buildColumns(t), [t])\n\n const { data, isLoading } = useQuery<EntitiesResponse>({\n queryKey: ['custom-entities', scopeVersion],\n queryFn: async () => {\n return readApiResultOrThrow<EntitiesResponse>('/api/entities/entities', undefined, {\n errorMessage: 'Failed to load entities',\n })\n },\n })\n\n const rowsAll = data?.items || []\n // Filter to only show user entities (source: 'custom')\n const userRows = rowsAll.filter(row => row.source === 'custom')\n const rows = React.useMemo(() => {\n if (!search) return userRows\n const q = search.toLowerCase()\n return userRows.filter(r => r.entityId.toLowerCase().includes(q) || r.label.toLowerCase().includes(q))\n }, [userRows, search])\n\n return (\n <DataTable\n title={t('entities.user.table.title', 'User Entities')}\n actions={(\n <>\n <Button variant=\"outline\" onClick={() => {\n const csv = buildEntitiesCsv(rows, { includeSidebar: true })\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = 'entities-user.csv'\n a.click()\n URL.revokeObjectURL(url)\n }}>{t('common.export', 'Export')}</Button>\n <Button asChild>\n <Link href=\"/backend/entities/user/create\">{t('common.create', 'Create')}</Link>\n </Button>\n </>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(v) => { setSearch(v); setPage(1) }}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n perspective={{ tableId: 'entities.user.list' }}\n emptyState={(\n <ListEmptyState\n entityName={t('entities.user.table.title', 'User Entities')}\n createHref=\"/backend/entities/user/create\"\n createLabel={t('common.create', 'Create')}\n />\n )}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: t('common.edit', 'Edit'), href: `/backend/entities/user/${encodeURIComponent(row.entityId)}` },\n { id: 'show-records', label: t('entities.user.table.actions.showRecords', 'Show records'), href: `/backend/entities/user/${encodeURIComponent(row.entityId)}/records` },\n ]}\n />\n )}\n pagination={{ page, pageSize: 50, total: rows.length, totalPages: 1, onPageChange: setPage }}\n isLoading={isLoading}\n />\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,26 @@
1
+ import { serializeExport } from "@open-mercato/shared/lib/crud/exporters";
2
+ function buildEntitiesCsv(rows, options) {
3
+ const includeSidebar = options?.includeSidebar ?? false;
4
+ const columns = [
5
+ { field: "entityId", header: "entityId" },
6
+ { field: "label", header: "label" },
7
+ { field: "source", header: "source" },
8
+ { field: "count", header: "count" }
9
+ ];
10
+ if (includeSidebar) columns.push({ field: "showInSidebar", header: "showInSidebar" });
11
+ const prepared = {
12
+ columns,
13
+ rows: rows.map((row) => ({
14
+ entityId: row.entityId,
15
+ label: row.label,
16
+ source: row.source,
17
+ count: row.count,
18
+ ...includeSidebar ? { showInSidebar: row.showInSidebar || false } : {}
19
+ }))
20
+ };
21
+ return serializeExport(prepared, "csv").body;
22
+ }
23
+ export {
24
+ buildEntitiesCsv
25
+ };
26
+ //# sourceMappingURL=entitiesCsvExport.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/entities/lib/entitiesCsvExport.ts"],
4
+ "sourcesContent": ["import { serializeExport, type CrudExportColumn, type PreparedExport } from '@open-mercato/shared/lib/crud/exporters'\n\nexport type EntitiesExportRow = {\n entityId: string\n label: string\n source: 'code' | 'custom'\n count: number\n showInSidebar?: boolean\n}\n\nexport function buildEntitiesCsv(rows: EntitiesExportRow[], options?: { includeSidebar?: boolean }): string {\n const includeSidebar = options?.includeSidebar ?? false\n const columns: CrudExportColumn[] = [\n { field: 'entityId', header: 'entityId' },\n { field: 'label', header: 'label' },\n { field: 'source', header: 'source' },\n { field: 'count', header: 'count' },\n ]\n if (includeSidebar) columns.push({ field: 'showInSidebar', header: 'showInSidebar' })\n const prepared: PreparedExport = {\n columns,\n rows: rows.map((row) => ({\n entityId: row.entityId,\n label: row.label,\n source: row.source,\n count: row.count,\n ...(includeSidebar ? { showInSidebar: row.showInSidebar || false } : {}),\n })),\n }\n return serializeExport(prepared, 'csv').body\n}\n"],
5
+ "mappings": "AAAA,SAAS,uBAAmE;AAUrE,SAAS,iBAAiB,MAA2B,SAAgD;AAC1G,QAAM,iBAAiB,SAAS,kBAAkB;AAClD,QAAM,UAA8B;AAAA,IAClC,EAAE,OAAO,YAAY,QAAQ,WAAW;AAAA,IACxC,EAAE,OAAO,SAAS,QAAQ,QAAQ;AAAA,IAClC,EAAE,OAAO,UAAU,QAAQ,SAAS;AAAA,IACpC,EAAE,OAAO,SAAS,QAAQ,QAAQ;AAAA,EACpC;AACA,MAAI,eAAgB,SAAQ,KAAK,EAAE,OAAO,iBAAiB,QAAQ,gBAAgB,CAAC;AACpF,QAAM,WAA2B;AAAA,IAC/B;AAAA,IACA,MAAM,KAAK,IAAI,CAAC,SAAS;AAAA,MACvB,UAAU,IAAI;AAAA,MACd,OAAO,IAAI;AAAA,MACX,QAAQ,IAAI;AAAA,MACZ,OAAO,IAAI;AAAA,MACX,GAAI,iBAAiB,EAAE,eAAe,IAAI,iBAAiB,MAAM,IAAI,CAAC;AAAA,IACxE,EAAE;AAAA,EACJ;AACA,SAAO,gBAAgB,UAAU,KAAK,EAAE;AAC1C;",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/staff/commands/shared.ts"],
4
- "sourcesContent": ["import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { StaffTeamMember } from '../data/entities'\n\nexport { ensureOrganizationScope, ensureTenantScope, extractUndoPayload }\n\nexport type StaffCommandScope = {\n tenantId: string | null\n organizationId: string | null\n requireTenant: boolean\n requireOrganization: boolean\n}\n\nexport function commandActorScope(ctx: CommandRuntimeContext): StaffCommandScope {\n const isPrivilegedActor = ctx.auth?.isSuperAdmin === true || ctx.systemActor === true\n const tenantId = isPrivilegedActor ? null : (ctx.auth?.tenantId ?? ctx.organizationScope?.tenantId ?? null)\n const organizationId = isPrivilegedActor ? null : (ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)\n const organizationUnrestricted =\n isPrivilegedActor || (organizationId === null && tenantId !== null && ctx.organizationScope?.allowedIds === null)\n return {\n tenantId,\n organizationId: organizationUnrestricted ? null : organizationId,\n requireTenant: !isPrivilegedActor,\n requireOrganization: !organizationUnrestricted,\n }\n}\n\nexport function explicitStaffCommandScope(tenantId: string | null, organizationId: string | null): StaffCommandScope {\n return {\n tenantId,\n organizationId,\n requireTenant: true,\n requireOrganization: true,\n }\n}\n\nexport function commandInputScope(ctx: CommandRuntimeContext, tenantId: string, organizationId: string): StaffCommandScope {\n if (ctx.auth?.isSuperAdmin === true || ctx.systemActor === true) {\n return explicitStaffCommandScope(tenantId, organizationId)\n }\n\n ensureTenantScope(ctx, tenantId)\n ensureOrganizationScope(ctx, organizationId)\n\n const actorTenantId = ctx.auth?.tenantId ?? ctx.organizationScope?.tenantId ?? null\n if (!actorTenantId || actorTenantId !== tenantId) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n\n if (!ctx.organizationScope) {\n const currentOrganizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n if (!currentOrganizationId || currentOrganizationId !== organizationId) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n }\n\n return explicitStaffCommandScope(tenantId, organizationId)\n}\n\nexport function applyScopeToWhere<TEntity extends object>(\n where: FilterQuery<TEntity>,\n scope: StaffCommandScope,\n): FilterQuery<TEntity> {\n const scoped = { ...(where as Record<string, unknown>) }\n if (scope.requireTenant || scope.tenantId !== null) scoped.tenantId = scope.tenantId\n if (scope.requireOrganization || scope.organizationId !== null) scoped.organizationId = scope.organizationId\n return scoped as FilterQuery<TEntity>\n}\n\nexport function scopeForDecryption(scope: StaffCommandScope): { tenantId: string | null; organizationId: string | null } {\n return { tenantId: scope.tenantId, organizationId: scope.organizationId }\n}\n\nexport async function requireTeamMember(\n em: EntityManager,\n memberId: string,\n scope: StaffCommandScope,\n message = 'Team member not found',\n): Promise<StaffTeamMember> {\n const member = await em.findOne(\n StaffTeamMember,\n applyScopeToWhere<StaffTeamMember>({ id: memberId, deletedAt: null }, scope),\n )\n if (!member) throw new CrudHttpError(404, { error: message })\n return member\n}\n"],
5
- "mappings": "AACA,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB,yBAAyB;AAC3D,SAAS,0BAA0B;AAEnC,SAAS,uBAAuB;AAWzB,SAAS,kBAAkB,KAA+C;AAC/E,QAAM,oBAAoB,IAAI,MAAM,iBAAiB,QAAQ,IAAI,gBAAgB;AACjF,QAAM,WAAW,oBAAoB,OAAQ,IAAI,MAAM,YAAY,IAAI,mBAAmB,YAAY;AACtG,QAAM,iBAAiB,oBAAoB,OAAQ,IAAI,0BAA0B,IAAI,MAAM,SAAS;AACpG,QAAM,2BACJ,qBAAsB,mBAAmB,QAAQ,aAAa,QAAQ,IAAI,mBAAmB,eAAe;AAC9G,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,2BAA2B,OAAO;AAAA,IAClD,eAAe,CAAC;AAAA,IAChB,qBAAqB,CAAC;AAAA,EACxB;AACF;AAEO,SAAS,0BAA0B,UAAyB,gBAAkD;AACnH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,qBAAqB;AAAA,EACvB;AACF;AAEO,SAAS,kBAAkB,KAA4B,UAAkB,gBAA2C;AACzH,MAAI,IAAI,MAAM,iBAAiB,QAAQ,IAAI,gBAAgB,MAAM;AAC/D,WAAO,0BAA0B,UAAU,cAAc;AAAA,EAC3D;AAEA,oBAAkB,KAAK,QAAQ;AAC/B,0BAAwB,KAAK,cAAc;AAE3C,QAAM,gBAAgB,IAAI,MAAM,YAAY,IAAI,mBAAmB,YAAY;AAC/E,MAAI,CAAC,iBAAiB,kBAAkB,UAAU;AAChD,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,EACrD;AAEA,MAAI,CAAC,IAAI,mBAAmB;AAC1B,UAAM,wBAAwB,IAAI,0BAA0B,IAAI,MAAM,SAAS;AAC/E,QAAI,CAAC,yBAAyB,0BAA0B,gBAAgB;AACtE,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,IACrD;AAAA,EACF;AAEA,SAAO,0BAA0B,UAAU,cAAc;AAC3D;AAEO,SAAS,kBACd,OACA,OACsB;AACtB,QAAM,SAAS,EAAE,GAAI,MAAkC;AACvD,MAAI,MAAM,iBAAiB,MAAM,aAAa,KAAM,QAAO,WAAW,MAAM;AAC5E,MAAI,MAAM,uBAAuB,MAAM,mBAAmB,KAAM,QAAO,iBAAiB,MAAM;AAC9F,SAAO;AACT;AAEO,SAAS,mBAAmB,OAAsF;AACvH,SAAO,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,eAAe;AAC1E;AAEA,eAAsB,kBACpB,IACA,UACA,OACA,UAAU,yBACgB;AAC1B,QAAM,SAAS,MAAM,GAAG;AAAA,IACtB;AAAA,IACA,kBAAmC,EAAE,IAAI,UAAU,WAAW,KAAK,GAAG,KAAK;AAAA,EAC7E;AACA,MAAI,CAAC,OAAQ,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AAC5D,SAAO;AACT;",
4
+ "sourcesContent": ["import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { StaffTeamMember } from '../data/entities'\n\nexport { ensureOrganizationScope, ensureTenantScope, extractUndoPayload }\n\nexport type StaffCommandScope = {\n tenantId: string | null\n organizationId: string | null\n requireTenant: boolean\n requireOrganization: boolean\n}\n\nexport function commandActorScope(ctx: CommandRuntimeContext): StaffCommandScope {\n const isPrivilegedActor = ctx.auth?.isSuperAdmin === true || ctx.systemActor === true\n const tenantId = isPrivilegedActor ? null : (ctx.auth?.tenantId ?? ctx.organizationScope?.tenantId ?? null)\n const organizationId = isPrivilegedActor ? null : (ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)\n const organizationUnrestricted =\n isPrivilegedActor || (organizationId === null && tenantId !== null && ctx.organizationScope?.allowedIds === null)\n return {\n tenantId,\n organizationId: organizationUnrestricted ? null : organizationId,\n requireTenant: !isPrivilegedActor,\n requireOrganization: !organizationUnrestricted,\n }\n}\n\nexport function explicitStaffCommandScope(tenantId: string | null, organizationId: string | null): StaffCommandScope {\n return {\n tenantId,\n organizationId,\n requireTenant: true,\n requireOrganization: true,\n }\n}\n\nexport function commandInputScope(ctx: CommandRuntimeContext, tenantId: string, organizationId: string): StaffCommandScope {\n if (ctx.auth?.isSuperAdmin === true || ctx.systemActor === true) {\n return explicitStaffCommandScope(tenantId, organizationId)\n }\n\n ensureTenantScope(ctx, tenantId)\n ensureOrganizationScope(ctx, organizationId)\n\n const actorTenantId = ctx.auth?.tenantId ?? ctx.organizationScope?.tenantId ?? null\n if (!actorTenantId || actorTenantId !== tenantId) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n\n if (!ctx.organizationScope) {\n const currentOrganizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n if (!currentOrganizationId || currentOrganizationId !== organizationId) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n }\n\n return explicitStaffCommandScope(tenantId, organizationId)\n}\n\nexport function applyScopeToWhere<TEntity extends object>(\n where: FilterQuery<TEntity>,\n scope: StaffCommandScope,\n): FilterQuery<TEntity> {\n const scoped = { ...(where as Record<string, unknown>) }\n if (scope.requireTenant || scope.tenantId !== null) scoped.tenantId = scope.tenantId\n if (scope.requireOrganization || scope.organizationId !== null) scoped.organizationId = scope.organizationId\n return scoped as FilterQuery<TEntity>\n}\n\nexport function scopeForDecryption(scope: StaffCommandScope): { tenantId: string | null; organizationId: string | null } {\n return { tenantId: scope.tenantId, organizationId: scope.organizationId }\n}\n\nexport async function requireTeamMember(\n em: EntityManager,\n memberId: string,\n scope: StaffCommandScope,\n message = 'Team member not found',\n): Promise<StaffTeamMember> {\n const member = await em.findOne(\n StaffTeamMember,\n applyScopeToWhere<StaffTeamMember>({ id: memberId, deletedAt: null }, scope),\n )\n if (!member) throw new CrudHttpError(404, { error: message })\n return member\n}\n"],
5
+ "mappings": "AACA,SAAS,qBAAqB;AAE9B,SAAS,yBAAyB,yBAAyB;AAC3D,SAAS,0BAA0B;AAEnC,SAAS,uBAAuB;AAWzB,SAAS,kBAAkB,KAA+C;AAC/E,QAAM,oBAAoB,IAAI,MAAM,iBAAiB,QAAQ,IAAI,gBAAgB;AACjF,QAAM,WAAW,oBAAoB,OAAQ,IAAI,MAAM,YAAY,IAAI,mBAAmB,YAAY;AACtG,QAAM,iBAAiB,oBAAoB,OAAQ,IAAI,0BAA0B,IAAI,MAAM,SAAS;AACpG,QAAM,2BACJ,qBAAsB,mBAAmB,QAAQ,aAAa,QAAQ,IAAI,mBAAmB,eAAe;AAC9G,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,2BAA2B,OAAO;AAAA,IAClD,eAAe,CAAC;AAAA,IAChB,qBAAqB,CAAC;AAAA,EACxB;AACF;AAEO,SAAS,0BAA0B,UAAyB,gBAAkD;AACnH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,qBAAqB;AAAA,EACvB;AACF;AAEO,SAAS,kBAAkB,KAA4B,UAAkB,gBAA2C;AACzH,MAAI,IAAI,MAAM,iBAAiB,QAAQ,IAAI,gBAAgB,MAAM;AAC/D,WAAO,0BAA0B,UAAU,cAAc;AAAA,EAC3D;AAEA,oBAAkB,KAAK,QAAQ;AAC/B,0BAAwB,KAAK,cAAc;AAE3C,QAAM,gBAAgB,IAAI,MAAM,YAAY,IAAI,mBAAmB,YAAY;AAC/E,MAAI,CAAC,iBAAiB,kBAAkB,UAAU;AAChD,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,EACrD;AAEA,MAAI,CAAC,IAAI,mBAAmB;AAC1B,UAAM,wBAAwB,IAAI,0BAA0B,IAAI,MAAM,SAAS;AAC/E,QAAI,CAAC,yBAAyB,0BAA0B,gBAAgB;AACtE,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,IACrD;AAAA,EACF;AAEA,SAAO,0BAA0B,UAAU,cAAc;AAC3D;AAEO,SAAS,kBACd,OACA,OACsB;AACtB,QAAM,SAAS,EAAE,GAAI,MAAkC;AACvD,MAAI,MAAM,iBAAiB,MAAM,aAAa,KAAM,QAAO,WAAW,MAAM;AAC5E,MAAI,MAAM,uBAAuB,MAAM,mBAAmB,KAAM,QAAO,iBAAiB,MAAM;AAC9F,SAAO;AACT;AAEO,SAAS,mBAAmB,OAAsF;AACvH,SAAO,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,eAAe;AAC1E;AAEA,eAAsB,kBACpB,IACA,UACA,OACA,UAAU,yBACgB;AAC1B,QAAM,SAAS,MAAM,GAAG;AAAA,IACtB;AAAA,IACA,kBAAmC,EAAE,IAAI,UAAU,WAAW,KAAK,GAAG,KAAK;AAAA,EAC7E;AACA,MAAI,CAAC,OAAQ,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AAC5D,SAAO;AACT;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6454.1.0647656f77",
3
+ "version": "0.6.6-develop.6457.1.41b39a465a",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -253,16 +253,16 @@
253
253
  "zod": "^4.4.3"
254
254
  },
255
255
  "peerDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6454.1.0647656f77",
257
- "@open-mercato/shared": "0.6.6-develop.6454.1.0647656f77",
258
- "@open-mercato/ui": "0.6.6-develop.6454.1.0647656f77",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6457.1.41b39a465a",
257
+ "@open-mercato/shared": "0.6.6-develop.6457.1.41b39a465a",
258
+ "@open-mercato/ui": "0.6.6-develop.6457.1.41b39a465a",
259
259
  "react": "^19.0.0",
260
260
  "react-dom": "^19.0.0"
261
261
  },
262
262
  "devDependencies": {
263
- "@open-mercato/ai-assistant": "0.6.6-develop.6454.1.0647656f77",
264
- "@open-mercato/shared": "0.6.6-develop.6454.1.0647656f77",
265
- "@open-mercato/ui": "0.6.6-develop.6454.1.0647656f77",
263
+ "@open-mercato/ai-assistant": "0.6.6-develop.6457.1.41b39a465a",
264
+ "@open-mercato/shared": "0.6.6-develop.6457.1.41b39a465a",
265
+ "@open-mercato/ui": "0.6.6-develop.6457.1.41b39a465a",
266
266
  "@testing-library/dom": "^10.4.1",
267
267
  "@testing-library/jest-dom": "^6.9.1",
268
268
  "@testing-library/react": "^16.3.1",
@@ -317,16 +317,17 @@
317
317
  "customer_accounts.signup.existing.subject": "You already have a portal account",
318
318
  "customer_accounts.signup.existing.title": "You already have a portal account",
319
319
  "customer_accounts.widgets.accountStatus": "Kontostatus",
320
+ "customer_accounts.widgets.accountStatus.description": "Zeigt den Status des Kundenportal-Kontos in der CRM-Personendetailansicht",
320
321
  "customer_accounts.widgets.emailVerified": "E-Mail verifiziert",
321
- "customer_accounts.widgets.invite.button": "Invite to Portal",
322
- "customer_accounts.widgets.invite.displayName": "Display Name",
323
- "customer_accounts.widgets.invite.error.emailRequired": "Email is required",
324
- "customer_accounts.widgets.invite.error.failed": "Failed to send invitation",
325
- "customer_accounts.widgets.invite.error.roleRequired": "At least one role must be selected",
326
- "customer_accounts.widgets.invite.noRoles": "No roles available",
327
- "customer_accounts.widgets.invite.roles": "Roles",
328
- "customer_accounts.widgets.invite.submit": "Send Invitation",
329
- "customer_accounts.widgets.invite.success": "Invitation sent successfully",
322
+ "customer_accounts.widgets.invite.button": "Zum Portal einladen",
323
+ "customer_accounts.widgets.invite.displayName": "Anzeigename",
324
+ "customer_accounts.widgets.invite.error.emailRequired": "E-Mail-Adresse ist erforderlich",
325
+ "customer_accounts.widgets.invite.error.failed": "Einladung konnte nicht gesendet werden",
326
+ "customer_accounts.widgets.invite.error.roleRequired": "Mindestens eine Rolle muss ausgewählt werden",
327
+ "customer_accounts.widgets.invite.noRoles": "Keine Rollen verfügbar",
328
+ "customer_accounts.widgets.invite.roles": "Rollen",
329
+ "customer_accounts.widgets.invite.submit": "Einladung senden",
330
+ "customer_accounts.widgets.invite.success": "Einladung erfolgreich gesendet",
330
331
  "customer_accounts.widgets.lastLogin": "Letzte Anmeldung",
331
332
  "customer_accounts.widgets.noAccount": "Kein verknüpftes Konto",
332
333
  "customer_accounts.widgets.noUsers": "Keine verknüpften Benutzer",
@@ -317,6 +317,7 @@
317
317
  "customer_accounts.signup.existing.subject": "You already have a portal account",
318
318
  "customer_accounts.signup.existing.title": "You already have a portal account",
319
319
  "customer_accounts.widgets.accountStatus": "Account status",
320
+ "customer_accounts.widgets.accountStatus.description": "Shows customer portal account status on CRM person detail",
320
321
  "customer_accounts.widgets.emailVerified": "Email verified",
321
322
  "customer_accounts.widgets.invite.button": "Invite to Portal",
322
323
  "customer_accounts.widgets.invite.displayName": "Display Name",
@@ -317,16 +317,17 @@
317
317
  "customer_accounts.signup.existing.subject": "You already have a portal account",
318
318
  "customer_accounts.signup.existing.title": "You already have a portal account",
319
319
  "customer_accounts.widgets.accountStatus": "Estado de la cuenta",
320
+ "customer_accounts.widgets.accountStatus.description": "Muestra el estado de la cuenta del portal del cliente en el detalle de la persona del CRM",
320
321
  "customer_accounts.widgets.emailVerified": "Correo verificado",
321
- "customer_accounts.widgets.invite.button": "Invite to Portal",
322
- "customer_accounts.widgets.invite.displayName": "Display Name",
323
- "customer_accounts.widgets.invite.error.emailRequired": "Email is required",
324
- "customer_accounts.widgets.invite.error.failed": "Failed to send invitation",
325
- "customer_accounts.widgets.invite.error.roleRequired": "At least one role must be selected",
326
- "customer_accounts.widgets.invite.noRoles": "No roles available",
322
+ "customer_accounts.widgets.invite.button": "Invitar al portal",
323
+ "customer_accounts.widgets.invite.displayName": "Nombre visible",
324
+ "customer_accounts.widgets.invite.error.emailRequired": "El correo electrónico es obligatorio",
325
+ "customer_accounts.widgets.invite.error.failed": "No se pudo enviar la invitación",
326
+ "customer_accounts.widgets.invite.error.roleRequired": "Debe seleccionar al menos un rol",
327
+ "customer_accounts.widgets.invite.noRoles": "No hay roles disponibles",
327
328
  "customer_accounts.widgets.invite.roles": "Roles",
328
- "customer_accounts.widgets.invite.submit": "Send Invitation",
329
- "customer_accounts.widgets.invite.success": "Invitation sent successfully",
329
+ "customer_accounts.widgets.invite.submit": "Enviar invitación",
330
+ "customer_accounts.widgets.invite.success": "Invitación enviada correctamente",
330
331
  "customer_accounts.widgets.lastLogin": "Último acceso",
331
332
  "customer_accounts.widgets.noAccount": "No hay cuenta vinculada",
332
333
  "customer_accounts.widgets.noUsers": "No hay usuarios vinculados",
@@ -317,16 +317,17 @@
317
317
  "customer_accounts.signup.existing.subject": "You already have a portal account",
318
318
  "customer_accounts.signup.existing.title": "You already have a portal account",
319
319
  "customer_accounts.widgets.accountStatus": "Status konta",
320
+ "customer_accounts.widgets.accountStatus.description": "Pokazuje status konta w portalu klienta na szczegółach osoby CRM",
320
321
  "customer_accounts.widgets.emailVerified": "E-mail zweryfikowany",
321
- "customer_accounts.widgets.invite.button": "Invite to Portal",
322
- "customer_accounts.widgets.invite.displayName": "Display Name",
323
- "customer_accounts.widgets.invite.error.emailRequired": "Email is required",
324
- "customer_accounts.widgets.invite.error.failed": "Failed to send invitation",
325
- "customer_accounts.widgets.invite.error.roleRequired": "At least one role must be selected",
326
- "customer_accounts.widgets.invite.noRoles": "No roles available",
327
- "customer_accounts.widgets.invite.roles": "Roles",
328
- "customer_accounts.widgets.invite.submit": "Send Invitation",
329
- "customer_accounts.widgets.invite.success": "Invitation sent successfully",
322
+ "customer_accounts.widgets.invite.button": "Zaproś do portalu",
323
+ "customer_accounts.widgets.invite.displayName": "Nazwa wyświetlana",
324
+ "customer_accounts.widgets.invite.error.emailRequired": "Adres e-mail jest wymagany",
325
+ "customer_accounts.widgets.invite.error.failed": "Nie udało się wysłać zaproszenia",
326
+ "customer_accounts.widgets.invite.error.roleRequired": "Musisz wybrać co najmniej jedną rolę",
327
+ "customer_accounts.widgets.invite.noRoles": "Brak dostępnych ról",
328
+ "customer_accounts.widgets.invite.roles": "Role",
329
+ "customer_accounts.widgets.invite.submit": "Wyślij zaproszenie",
330
+ "customer_accounts.widgets.invite.success": "Zaproszenie zostało wysłane",
330
331
  "customer_accounts.widgets.lastLogin": "Ostatnie logowanie",
331
332
  "customer_accounts.widgets.noAccount": "Brak powiązanego konta",
332
333
  "customer_accounts.widgets.noUsers": "Brak powiązanych użytkowników",
@@ -5,7 +5,7 @@ const widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unkn
5
5
  metadata: {
6
6
  id: 'customer_accounts.injection.account-status',
7
7
  title: 'Customer Account Status',
8
- description: 'Shows customer portal account status on CRM person detail',
8
+ description: 'customer_accounts.widgets.accountStatus.description',
9
9
  features: ['customer_accounts.view'],
10
10
  priority: 100,
11
11
  enabled: true,
@@ -5,6 +5,18 @@ import { invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'
5
5
  import { runWithCacheTenant } from '@open-mercato/cache'
6
6
  import { createModuleQueue, type Queue } from '@open-mercato/queue'
7
7
  import type { ProgressService, ProgressServiceContext } from '../../progress/lib/progressService'
8
+ // Eagerly register the deal command handlers into this module graph's command
9
+ // registry. The bulk workers dispatch `customers.deals.update` through the command
10
+ // bus, but command handlers became lazy-loaded (#3703): they are only reachable if
11
+ // their generated loader was registered into the SAME `@open-mercato/shared` instance
12
+ // the worker's bus reads. A queue worker runs in its own container/process and must
13
+ // not depend on that external lazy registration having reached its instance — in the
14
+ // standalone integration harness the loader registry and the worker's bus can resolve
15
+ // to different `@open-mercato/shared` instances, leaving the lazy loader unreachable
16
+ // (issue: bulk deal jobs fail with "Command handler not registered for id
17
+ // customers.deals.update"). Importing the command module here registers the handlers
18
+ // through the worker's own import graph, so the dispatch always resolves.
19
+ import '../commands/deals'
8
20
 
9
21
  export const CUSTOMERS_DEALS_BULK_UPDATE_STAGE_QUEUE = 'customers-deals-bulk-update-stage'
10
22
  export const CUSTOMERS_DEALS_BULK_UPDATE_OWNER_QUEUE = 'customers-deals-bulk-update-owner'
@@ -7,6 +7,7 @@ import { RowActions } from '@open-mercato/ui/backend/RowActions'
7
7
  import { Button } from '@open-mercato/ui/primitives/button'
8
8
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
9
9
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
10
+ import { buildEntitiesCsv } from '../lib/entitiesCsvExport'
10
11
 
11
12
  type EntityRow = {
12
13
  entityId: string
@@ -24,18 +25,6 @@ const columns: ColumnDef<EntityRow>[] = [
24
25
  { accessorKey: 'count', header: 'Fields', meta: { priority: 4 } },
25
26
  ]
26
27
 
27
- function toCsv(rows: EntityRow[]) {
28
- const header = ['entityId','label','source','count']
29
- const esc = (s: string | number) => {
30
- const str = String(s ?? '')
31
- if (/[",\n]/.test(str)) return '"' + str.replace(/"/g, '""') + '"'
32
- return str
33
- }
34
- const lines = [header.join(',')]
35
- for (const r of rows) lines.push([r.entityId, r.label, r.source, r.count].map(esc).join(','))
36
- return lines.join('\n')
37
- }
38
-
39
28
  export default function SystemEntitiesTable() {
40
29
  const [sorting, setSorting] = React.useState<SortingState>([{ id: 'entityId', desc: false }])
41
30
  const [page, setPage] = React.useState(1)
@@ -66,7 +55,7 @@ export default function SystemEntitiesTable() {
66
55
  actions={(
67
56
  <>
68
57
  <Button variant="outline" onClick={() => {
69
- const csv = toCsv(rows)
58
+ const csv = buildEntitiesCsv(rows)
70
59
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
71
60
  const url = URL.createObjectURL(blob)
72
61
  const a = document.createElement('a')
@@ -9,6 +9,7 @@ import { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'
9
9
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
10
10
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
11
11
  import { useT } from '@open-mercato/shared/lib/i18n/context'
12
+ import { buildEntitiesCsv } from '../lib/entitiesCsvExport'
12
13
 
13
14
  type EntityRow = {
14
15
  entityId: string
@@ -41,18 +42,6 @@ function buildColumns(t: (key: string, fallback: string) => string): ColumnDef<E
41
42
  ]
42
43
  }
43
44
 
44
- function toCsv(rows: EntityRow[]) {
45
- const header = ['entityId','label','source','count','showInSidebar']
46
- const esc = (s: string | number | boolean) => {
47
- const str = String(s ?? '')
48
- if (/[",\n]/.test(str)) return '"' + str.replace(/"/g, '""') + '"'
49
- return str
50
- }
51
- const lines = [header.join(',')]
52
- for (const r of rows) lines.push([r.entityId, r.label, r.source, r.count, r.showInSidebar || false].map(esc).join(','))
53
- return lines.join('\n')
54
- }
55
-
56
45
  export default function UserEntitiesTable() {
57
46
  const t = useT()
58
47
  const [sorting, setSorting] = React.useState<SortingState>([{ id: 'entityId', desc: false }])
@@ -86,7 +75,7 @@ export default function UserEntitiesTable() {
86
75
  actions={(
87
76
  <>
88
77
  <Button variant="outline" onClick={() => {
89
- const csv = toCsv(rows)
78
+ const csv = buildEntitiesCsv(rows, { includeSidebar: true })
90
79
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
91
80
  const url = URL.createObjectURL(blob)
92
81
  const a = document.createElement('a')
@@ -0,0 +1,31 @@
1
+ import { serializeExport, type CrudExportColumn, type PreparedExport } from '@open-mercato/shared/lib/crud/exporters'
2
+
3
+ export type EntitiesExportRow = {
4
+ entityId: string
5
+ label: string
6
+ source: 'code' | 'custom'
7
+ count: number
8
+ showInSidebar?: boolean
9
+ }
10
+
11
+ export function buildEntitiesCsv(rows: EntitiesExportRow[], options?: { includeSidebar?: boolean }): string {
12
+ const includeSidebar = options?.includeSidebar ?? false
13
+ const columns: CrudExportColumn[] = [
14
+ { field: 'entityId', header: 'entityId' },
15
+ { field: 'label', header: 'label' },
16
+ { field: 'source', header: 'source' },
17
+ { field: 'count', header: 'count' },
18
+ ]
19
+ if (includeSidebar) columns.push({ field: 'showInSidebar', header: 'showInSidebar' })
20
+ const prepared: PreparedExport = {
21
+ columns,
22
+ rows: rows.map((row) => ({
23
+ entityId: row.entityId,
24
+ label: row.label,
25
+ source: row.source,
26
+ count: row.count,
27
+ ...(includeSidebar ? { showInSidebar: row.showInSidebar || false } : {}),
28
+ })),
29
+ }
30
+ return serializeExport(prepared, 'csv').body
31
+ }
@@ -1,5 +1,6 @@
1
1
  import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
2
2
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
3
+ import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
3
4
  import { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
4
5
  import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
5
6
  import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'