@open-mercato/core 0.6.6-develop.6456.1.d4b0c54321 → 0.6.6-develop.6458.1.113a54fb91
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,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;
|
|
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
|
}
|
|
@@ -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
|
|
5
|
-
"mappings": "AACA,SAAS,qBAAqB;
|
|
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;",
|
|
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.
|
|
3
|
+
"version": "0.6.6-develop.6458.1.113a54fb91",
|
|
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.
|
|
257
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
258
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6458.1.113a54fb91",
|
|
257
|
+
"@open-mercato/shared": "0.6.6-develop.6458.1.113a54fb91",
|
|
258
|
+
"@open-mercato/ui": "0.6.6-develop.6458.1.113a54fb91",
|
|
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.
|
|
264
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
265
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
263
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6458.1.113a54fb91",
|
|
264
|
+
"@open-mercato/shared": "0.6.6-develop.6458.1.113a54fb91",
|
|
265
|
+
"@open-mercato/ui": "0.6.6-develop.6458.1.113a54fb91",
|
|
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",
|
|
@@ -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'
|
|
@@ -1,6 +1,5 @@
|
|
|
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'
|
|
4
3
|
import { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
|
|
5
4
|
import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
|
|
6
5
|
import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
|