@open-mercato/sync-akeneo 0.6.8-develop.6986.1.3adb0d0df6 → 0.6.8-develop.6992.1.00c90fecff

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.
@@ -88,6 +88,7 @@ async function deleteImportedProductsWithProgress(params) {
88
88
  const em = container.resolve("em");
89
89
  const commandBus = container.resolve("commandBus");
90
90
  const progressService = container.resolve("progressService");
91
+ const syncRunService = container.resolve("dataSyncRunService");
91
92
  const progressContext = {
92
93
  tenantId: scope.tenantId,
93
94
  organizationId: scope.organizationId,
@@ -140,6 +141,10 @@ async function deleteImportedProductsWithProgress(params) {
140
141
  organizationId: scope.organizationId,
141
142
  tenantId: scope.tenantId
142
143
  });
144
+ await syncRunService.resetResumePosition("sync_akeneo", "products", "import", {
145
+ organizationId: scope.organizationId,
146
+ tenantId: scope.tenantId
147
+ });
143
148
  const summary = buildSummary(productIds.length, deletedProductCount);
144
149
  await progressService.completeJob(progressJobId, { resultSummary: summary }, progressContext);
145
150
  return summary;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/sync_akeneo/lib/delete-imported-products.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { ProgressService, ProgressServiceContext } from '@open-mercato/core/modules/progress/lib/progressService'\nimport { CatalogProduct } from '@open-mercato/core/modules/catalog/data/entities'\nimport { SyncCursor } from '@open-mercato/core/modules/data_sync/data/entities'\nimport { SyncExternalIdMapping } from '@open-mercato/core/modules/integrations/data/entities'\n\nexport const AKENEO_DELETE_IMPORTED_PRODUCTS_QUEUE = 'sync-akeneo-delete-products'\n\nconst PRODUCT_MAPPING_ENTITY_TYPES = [\n 'catalog_product',\n 'catalog_product_variant',\n 'catalog_offer',\n 'catalog_product_price',\n 'attachment',\n] as const\n\nexport type DeleteImportedProductsScope = {\n organizationId: string\n tenantId: string\n userId?: string | null\n}\n\nexport type DeleteImportedProductsJobPayload = {\n progressJobId: string\n scope: DeleteImportedProductsScope\n}\n\nexport type DeleteImportedProductsSummary = {\n message: string\n requestedProductCount: number\n deletedProductCount: number\n skippedProductCount: number\n}\n\nfunction buildCommandContext(\n scope: DeleteImportedProductsScope,\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\nfunction buildSummary(requestedProductCount: number, deletedProductCount: number): DeleteImportedProductsSummary {\n const skippedProductCount = Math.max(0, requestedProductCount - deletedProductCount)\n const baseMessage = deletedProductCount === 1\n ? 'Deleted 1 Akeneo-imported product.'\n : `Deleted ${deletedProductCount} Akeneo-imported products.`\n\n if (skippedProductCount <= 0) {\n return {\n message: baseMessage,\n requestedProductCount,\n deletedProductCount,\n skippedProductCount,\n }\n }\n\n return {\n message: `${baseMessage} Skipped ${skippedProductCount}.`,\n requestedProductCount,\n deletedProductCount,\n skippedProductCount,\n }\n}\n\nexport async function findAkeneoImportedProductIds(\n em: EntityManager,\n scope: Pick<DeleteImportedProductsScope, 'organizationId' | 'tenantId'>,\n): Promise<string[]> {\n const productMappings = await findWithDecryption(\n em,\n SyncExternalIdMapping,\n {\n integrationId: 'sync_akeneo',\n internalEntityType: 'catalog_product',\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n {\n fields: ['internalEntityId', 'createdAt'],\n orderBy: { createdAt: 'asc' },\n },\n scope,\n )\n\n const metadataProducts = await findWithDecryption(\n em,\n CatalogProduct,\n {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n metadata: {\n source: 'akeneo',\n },\n },\n {\n fields: ['id'],\n orderBy: { createdAt: 'asc' },\n },\n scope,\n )\n\n return Array.from(new Set(\n [\n ...(productMappings as Array<{ internalEntityId: string }>).map((entry) => entry.internalEntityId),\n ...(metadataProducts as Array<{ id: string }>).map((entry) => entry.id),\n ],\n ))\n}\n\nexport async function deleteImportedProductsWithProgress(params: {\n container: AwilixContainer\n progressJobId: string\n scope: DeleteImportedProductsScope\n}): Promise<DeleteImportedProductsSummary> {\n const { container, progressJobId, scope } = params\n const em = container.resolve('em') as EntityManager\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\n const productIds = await findAkeneoImportedProductIds(em, scope)\n if (productIds.length > 0) {\n await progressService.updateProgress(\n progressJobId,\n { totalCount: productIds.length, processedCount: 0 },\n progressContext,\n )\n }\n\n if (productIds.length === 0) {\n const summary = buildSummary(0, 0)\n await progressService.completeJob(progressJobId, { resultSummary: summary }, progressContext)\n return summary\n }\n\n const commandContext = buildCommandContext(scope, container)\n let deletedProductCount = 0\n\n for (const [index, productId] of productIds.entries()) {\n try {\n await commandBus.execute<{ body?: Record<string, unknown> }, { productId: string }>('catalog.products.delete', {\n input: { body: { id: productId } },\n ctx: commandContext,\n })\n deletedProductCount += 1\n } catch {}\n\n await progressService.updateProgress(\n progressJobId,\n {\n totalCount: productIds.length,\n processedCount: index + 1,\n },\n progressContext,\n )\n }\n\n await em.nativeDelete(SyncExternalIdMapping, {\n integrationId: 'sync_akeneo',\n internalEntityType: { $in: [...PRODUCT_MAPPING_ENTITY_TYPES] },\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n\n await em.nativeDelete(SyncCursor, {\n integrationId: 'sync_akeneo',\n entityType: 'products',\n direction: 'import',\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n\n const summary = buildSummary(productIds.length, deletedProductCount)\n await progressService.completeJob(progressJobId, { resultSummary: summary }, progressContext)\n\n return summary\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { ProgressService, ProgressServiceContext } from '@open-mercato/core/modules/progress/lib/progressService'\nimport { CatalogProduct } from '@open-mercato/core/modules/catalog/data/entities'\nimport { SyncCursor } from '@open-mercato/core/modules/data_sync/data/entities'\nimport type { SyncRunService } from '@open-mercato/core/modules/data_sync/lib/sync-run-service'\nimport { SyncExternalIdMapping } from '@open-mercato/core/modules/integrations/data/entities'\n\nexport const AKENEO_DELETE_IMPORTED_PRODUCTS_QUEUE = 'sync-akeneo-delete-products'\n\nconst PRODUCT_MAPPING_ENTITY_TYPES = [\n 'catalog_product',\n 'catalog_product_variant',\n 'catalog_offer',\n 'catalog_product_price',\n 'attachment',\n] as const\n\nexport type DeleteImportedProductsScope = {\n organizationId: string\n tenantId: string\n userId?: string | null\n}\n\nexport type DeleteImportedProductsJobPayload = {\n progressJobId: string\n scope: DeleteImportedProductsScope\n}\n\nexport type DeleteImportedProductsSummary = {\n message: string\n requestedProductCount: number\n deletedProductCount: number\n skippedProductCount: number\n}\n\nfunction buildCommandContext(\n scope: DeleteImportedProductsScope,\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\nfunction buildSummary(requestedProductCount: number, deletedProductCount: number): DeleteImportedProductsSummary {\n const skippedProductCount = Math.max(0, requestedProductCount - deletedProductCount)\n const baseMessage = deletedProductCount === 1\n ? 'Deleted 1 Akeneo-imported product.'\n : `Deleted ${deletedProductCount} Akeneo-imported products.`\n\n if (skippedProductCount <= 0) {\n return {\n message: baseMessage,\n requestedProductCount,\n deletedProductCount,\n skippedProductCount,\n }\n }\n\n return {\n message: `${baseMessage} Skipped ${skippedProductCount}.`,\n requestedProductCount,\n deletedProductCount,\n skippedProductCount,\n }\n}\n\nexport async function findAkeneoImportedProductIds(\n em: EntityManager,\n scope: Pick<DeleteImportedProductsScope, 'organizationId' | 'tenantId'>,\n): Promise<string[]> {\n const productMappings = await findWithDecryption(\n em,\n SyncExternalIdMapping,\n {\n integrationId: 'sync_akeneo',\n internalEntityType: 'catalog_product',\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n {\n fields: ['internalEntityId', 'createdAt'],\n orderBy: { createdAt: 'asc' },\n },\n scope,\n )\n\n const metadataProducts = await findWithDecryption(\n em,\n CatalogProduct,\n {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n metadata: {\n source: 'akeneo',\n },\n },\n {\n fields: ['id'],\n orderBy: { createdAt: 'asc' },\n },\n scope,\n )\n\n return Array.from(new Set(\n [\n ...(productMappings as Array<{ internalEntityId: string }>).map((entry) => entry.internalEntityId),\n ...(metadataProducts as Array<{ id: string }>).map((entry) => entry.id),\n ],\n ))\n}\n\nexport async function deleteImportedProductsWithProgress(params: {\n container: AwilixContainer\n progressJobId: string\n scope: DeleteImportedProductsScope\n}): Promise<DeleteImportedProductsSummary> {\n const { container, progressJobId, scope } = params\n const em = container.resolve('em') as EntityManager\n const commandBus = container.resolve('commandBus') as CommandBus\n const progressService = container.resolve('progressService') as ProgressService\n const syncRunService = container.resolve('dataSyncRunService') as SyncRunService\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\n const productIds = await findAkeneoImportedProductIds(em, scope)\n if (productIds.length > 0) {\n await progressService.updateProgress(\n progressJobId,\n { totalCount: productIds.length, processedCount: 0 },\n progressContext,\n )\n }\n\n if (productIds.length === 0) {\n const summary = buildSummary(0, 0)\n await progressService.completeJob(progressJobId, { resultSummary: summary }, progressContext)\n return summary\n }\n\n const commandContext = buildCommandContext(scope, container)\n let deletedProductCount = 0\n\n for (const [index, productId] of productIds.entries()) {\n try {\n await commandBus.execute<{ body?: Record<string, unknown> }, { productId: string }>('catalog.products.delete', {\n input: { body: { id: productId } },\n ctx: commandContext,\n })\n deletedProductCount += 1\n } catch {}\n\n await progressService.updateProgress(\n progressJobId,\n {\n totalCount: productIds.length,\n processedCount: index + 1,\n },\n progressContext,\n )\n }\n\n await em.nativeDelete(SyncExternalIdMapping, {\n integrationId: 'sync_akeneo',\n internalEntityType: { $in: [...PRODUCT_MAPPING_ENTITY_TYPES] },\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n\n await em.nativeDelete(SyncCursor, {\n integrationId: 'sync_akeneo',\n entityType: 'products',\n direction: 'import',\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n\n // Deleting the shared row only resets entity types that write one. Clear the\n // run-scoped position too, so this stays a full reset if the products adapter\n // ever opts out of the shared cursor.\n await syncRunService.resetResumePosition('sync_akeneo', 'products', 'import', {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n\n const summary = buildSummary(productIds.length, deletedProductCount)\n await progressService.completeJob(progressJobId, { resultSummary: summary }, progressContext)\n\n return summary\n}\n"],
5
+ "mappings": "AAGA,SAAS,0BAA0B;AAEnC,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAE3B,SAAS,6BAA6B;AAE/B,MAAM,wCAAwC;AAErD,MAAM,+BAA+B;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAoBA,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,SAAS,aAAa,uBAA+B,qBAA4D;AAC/G,QAAM,sBAAsB,KAAK,IAAI,GAAG,wBAAwB,mBAAmB;AACnF,QAAM,cAAc,wBAAwB,IACxC,uCACA,WAAW,mBAAmB;AAElC,MAAI,uBAAuB,GAAG;AAC5B,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,GAAG,WAAW,YAAY,mBAAmB;AAAA,IACtD;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,6BACpB,IACA,OACmB;AACnB,QAAM,kBAAkB,MAAM;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,MACE,eAAe;AAAA,MACf,oBAAoB;AAAA,MACpB,gBAAgB,MAAM;AAAA,MACtB,UAAU,MAAM;AAAA,MAChB,WAAW;AAAA,IACb;AAAA,IACA;AAAA,MACE,QAAQ,CAAC,oBAAoB,WAAW;AAAA,MACxC,SAAS,EAAE,WAAW,MAAM;AAAA,IAC9B;AAAA,IACA;AAAA,EACF;AAEA,QAAM,mBAAmB,MAAM;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,MACE,gBAAgB,MAAM;AAAA,MACtB,UAAU,MAAM;AAAA,MAChB,WAAW;AAAA,MACX,UAAU;AAAA,QACR,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA;AAAA,MACE,QAAQ,CAAC,IAAI;AAAA,MACb,SAAS,EAAE,WAAW,MAAM;AAAA,IAC9B;AAAA,IACA;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,oBAAI;AAAA,IACpB;AAAA,MACE,GAAI,gBAAwD,IAAI,CAAC,UAAU,MAAM,gBAAgB;AAAA,MACjG,GAAI,iBAA2C,IAAI,CAAC,UAAU,MAAM,EAAE;AAAA,IACxE;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,mCAAmC,QAId;AACzC,QAAM,EAAE,WAAW,eAAe,MAAM,IAAI;AAC5C,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,aAAa,UAAU,QAAQ,YAAY;AACjD,QAAM,kBAAkB,UAAU,QAAQ,iBAAiB;AAC3D,QAAM,iBAAiB,UAAU,QAAQ,oBAAoB;AAC7D,QAAM,kBAA0C;AAAA,IAC9C,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,QAAQ,MAAM;AAAA,EAChB;AAEA,QAAM,gBAAgB,SAAS,eAAe,eAAe;AAE7D,QAAM,aAAa,MAAM,6BAA6B,IAAI,KAAK;AAC/D,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,gBAAgB;AAAA,MACpB;AAAA,MACA,EAAE,YAAY,WAAW,QAAQ,gBAAgB,EAAE;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAEA,MAAI,WAAW,WAAW,GAAG;AAC3B,UAAMA,WAAU,aAAa,GAAG,CAAC;AACjC,UAAM,gBAAgB,YAAY,eAAe,EAAE,eAAeA,SAAQ,GAAG,eAAe;AAC5F,WAAOA;AAAA,EACT;AAEA,QAAM,iBAAiB,oBAAoB,OAAO,SAAS;AAC3D,MAAI,sBAAsB;AAE1B,aAAW,CAAC,OAAO,SAAS,KAAK,WAAW,QAAQ,GAAG;AACrD,QAAI;AACF,YAAM,WAAW,QAAmE,2BAA2B;AAAA,QAC7G,OAAO,EAAE,MAAM,EAAE,IAAI,UAAU,EAAE;AAAA,QACjC,KAAK;AAAA,MACP,CAAC;AACD,6BAAuB;AAAA,IACzB,QAAQ;AAAA,IAAC;AAET,UAAM,gBAAgB;AAAA,MACpB;AAAA,MACA;AAAA,QACE,YAAY,WAAW;AAAA,QACvB,gBAAgB,QAAQ;AAAA,MAC1B;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,GAAG,aAAa,uBAAuB;AAAA,IAC3C,eAAe;AAAA,IACf,oBAAoB,EAAE,KAAK,CAAC,GAAG,4BAA4B,EAAE;AAAA,IAC7D,gBAAgB,MAAM;AAAA,IACtB,UAAU,MAAM;AAAA,EAClB,CAAC;AAED,QAAM,GAAG,aAAa,YAAY;AAAA,IAChC,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,gBAAgB,MAAM;AAAA,IACtB,UAAU,MAAM;AAAA,EAClB,CAAC;AAKD,QAAM,eAAe,oBAAoB,eAAe,YAAY,UAAU;AAAA,IAC5E,gBAAgB,MAAM;AAAA,IACtB,UAAU,MAAM;AAAA,EAClB,CAAC;AAED,QAAM,UAAU,aAAa,WAAW,QAAQ,mBAAmB;AACnE,QAAM,gBAAgB,YAAY,eAAe,EAAE,eAAe,QAAQ,GAAG,eAAe;AAE5F,SAAO;AACT;",
6
6
  "names": ["summary"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/sync-akeneo",
3
- "version": "0.6.8-develop.6986.1.3adb0d0df6",
3
+ "version": "0.6.8-develop.6992.1.00c90fecff",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,18 +53,18 @@
53
53
  }
54
54
  },
55
55
  "dependencies": {
56
- "@open-mercato/core": "0.6.8-develop.6986.1.3adb0d0df6",
57
- "@open-mercato/ui": "0.6.8-develop.6986.1.3adb0d0df6",
56
+ "@open-mercato/core": "0.6.8-develop.6992.1.00c90fecff",
57
+ "@open-mercato/ui": "0.6.8-develop.6992.1.00c90fecff",
58
58
  "node-html-markdown": "^2.0.0"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@mikro-orm/postgresql": "^7.0.14",
62
- "@open-mercato/shared": "0.6.8-develop.6986.1.3adb0d0df6",
62
+ "@open-mercato/shared": "0.6.8-develop.6992.1.00c90fecff",
63
63
  "react": "^19.0.0",
64
64
  "react-dom": "^19.0.0"
65
65
  },
66
66
  "devDependencies": {
67
- "@open-mercato/shared": "0.6.8-develop.6986.1.3adb0d0df6",
67
+ "@open-mercato/shared": "0.6.8-develop.6992.1.00c90fecff",
68
68
  "@types/jest": "^30.0.0",
69
69
  "@types/react": "^19.2.17",
70
70
  "@types/react-dom": "^19.2.3",
@@ -0,0 +1,74 @@
1
+ /** @jest-environment node */
2
+
3
+ import type { AwilixContainer } from 'awilix'
4
+ import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
5
+ import { SyncCursor } from '@open-mercato/core/modules/data_sync/data/entities'
6
+
7
+ jest.mock('@open-mercato/shared/lib/encryption/find', () => ({
8
+ findWithDecryption: jest.fn(),
9
+ findOneWithDecryption: jest.fn(),
10
+ }))
11
+
12
+ import { deleteImportedProductsWithProgress } from '../lib/delete-imported-products'
13
+
14
+ const SCOPE = { organizationId: 'org-1', tenantId: 'tenant-1', userId: 'user-1' }
15
+
16
+ function buildHarness(productIds: string[]) {
17
+ ;(findWithDecryption as jest.Mock).mockResolvedValue(
18
+ productIds.map((id) => ({ internalEntityId: id, createdAt: new Date('2026-08-01') })),
19
+ )
20
+
21
+ const em = {
22
+ nativeDelete: jest.fn(async () => 1),
23
+ }
24
+ const resetResumePosition = jest.fn(async () => 1)
25
+ const services: Record<string, unknown> = {
26
+ em,
27
+ commandBus: { execute: jest.fn(async () => ({ productId: 'p' })) },
28
+ progressService: {
29
+ startJob: jest.fn(async () => undefined),
30
+ updateProgress: jest.fn(async () => undefined),
31
+ completeJob: jest.fn(async () => undefined),
32
+ },
33
+ dataSyncRunService: { resetResumePosition },
34
+ }
35
+ const container = { resolve: (key: string) => services[key] } as unknown as AwilixContainer
36
+
37
+ return { container, em, resetResumePosition }
38
+ }
39
+
40
+ describe('deleteImportedProductsWithProgress resets both cursor surfaces', () => {
41
+ beforeEach(() => {
42
+ jest.clearAllMocks()
43
+ })
44
+
45
+ it('clears the run-scoped resume position alongside the shared cursor row', async () => {
46
+ const { container, em, resetResumePosition } = buildHarness(['product-1'])
47
+
48
+ await deleteImportedProductsWithProgress({ container, progressJobId: 'job-1', scope: SCOPE })
49
+
50
+ expect(em.nativeDelete).toHaveBeenCalledWith(SyncCursor, expect.objectContaining({
51
+ integrationId: 'sync_akeneo',
52
+ entityType: 'products',
53
+ direction: 'import',
54
+ organizationId: 'org-1',
55
+ tenantId: 'tenant-1',
56
+ }))
57
+ // Deleting the shared row only resets entity types that write one; without
58
+ // this the reset would leave an interrupted run's cursor as the next start
59
+ // position if the products adapter ever opts out.
60
+ expect(resetResumePosition).toHaveBeenCalledWith('sync_akeneo', 'products', 'import', {
61
+ organizationId: 'org-1',
62
+ tenantId: 'tenant-1',
63
+ })
64
+ })
65
+
66
+ it('does not reset anything when there was nothing imported to delete', async () => {
67
+ const { container, em, resetResumePosition } = buildHarness([])
68
+
69
+ await deleteImportedProductsWithProgress({ container, progressJobId: 'job-1', scope: SCOPE })
70
+
71
+ expect(em.nativeDelete).not.toHaveBeenCalled()
72
+ expect(resetResumePosition).not.toHaveBeenCalled()
73
+ })
74
+ })
@@ -5,6 +5,7 @@ import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
5
5
  import type { ProgressService, ProgressServiceContext } from '@open-mercato/core/modules/progress/lib/progressService'
6
6
  import { CatalogProduct } from '@open-mercato/core/modules/catalog/data/entities'
7
7
  import { SyncCursor } from '@open-mercato/core/modules/data_sync/data/entities'
8
+ import type { SyncRunService } from '@open-mercato/core/modules/data_sync/lib/sync-run-service'
8
9
  import { SyncExternalIdMapping } from '@open-mercato/core/modules/integrations/data/entities'
9
10
 
10
11
  export const AKENEO_DELETE_IMPORTED_PRODUCTS_QUEUE = 'sync-akeneo-delete-products'
@@ -132,6 +133,7 @@ export async function deleteImportedProductsWithProgress(params: {
132
133
  const em = container.resolve('em') as EntityManager
133
134
  const commandBus = container.resolve('commandBus') as CommandBus
134
135
  const progressService = container.resolve('progressService') as ProgressService
136
+ const syncRunService = container.resolve('dataSyncRunService') as SyncRunService
135
137
  const progressContext: ProgressServiceContext = {
136
138
  tenantId: scope.tenantId,
137
139
  organizationId: scope.organizationId,
@@ -192,6 +194,14 @@ export async function deleteImportedProductsWithProgress(params: {
192
194
  tenantId: scope.tenantId,
193
195
  })
194
196
 
197
+ // Deleting the shared row only resets entity types that write one. Clear the
198
+ // run-scoped position too, so this stays a full reset if the products adapter
199
+ // ever opts out of the shared cursor.
200
+ await syncRunService.resetResumePosition('sync_akeneo', 'products', 'import', {
201
+ organizationId: scope.organizationId,
202
+ tenantId: scope.tenantId,
203
+ })
204
+
195
205
  const summary = buildSummary(productIds.length, deletedProductCount)
196
206
  await progressService.completeJob(progressJobId, { resultSummary: summary }, progressContext)
197
207