@open-mercato/core 0.6.7-develop.6653.1.b5bc7194a0 → 0.6.7-develop.6660.1.90e1e2eef6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/business_rules/lib/value-resolver.js +7 -0
  3. package/dist/modules/business_rules/lib/value-resolver.js.map +2 -2
  4. package/dist/modules/currencies/commands/currencies.js +22 -8
  5. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  6. package/dist/modules/currencies/commands/exchange-rates.js +22 -8
  7. package/dist/modules/currencies/commands/exchange-rates.js.map +2 -2
  8. package/dist/modules/currencies/commands/scope.js +22 -0
  9. package/dist/modules/currencies/commands/scope.js.map +7 -0
  10. package/dist/modules/customers/components/detail/ActivitiesSection.js +5 -4
  11. package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
  12. package/dist/modules/customers/components/detail/ActivityHistorySection.js +5 -4
  13. package/dist/modules/customers/components/detail/ActivityHistorySection.js.map +2 -2
  14. package/dist/modules/feature_toggles/api/global/route.js +6 -0
  15. package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
  16. package/dist/modules/feature_toggles/commands/global.js +9 -9
  17. package/dist/modules/feature_toggles/commands/global.js.map +2 -2
  18. package/dist/modules/query_index/lib/subscriber-scope.js +63 -10
  19. package/dist/modules/query_index/lib/subscriber-scope.js.map +2 -2
  20. package/dist/modules/query_index/subscribers/delete_one.js +20 -6
  21. package/dist/modules/query_index/subscribers/delete_one.js.map +2 -2
  22. package/dist/modules/query_index/subscribers/upsert_one.js +8 -3
  23. package/dist/modules/query_index/subscribers/upsert_one.js.map +2 -2
  24. package/dist/modules/workflows/api/definitions/[id]/customize/route.js +2 -0
  25. package/dist/modules/workflows/api/definitions/[id]/customize/route.js.map +2 -2
  26. package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js +4 -0
  27. package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js.map +2 -2
  28. package/package.json +7 -7
  29. package/src/modules/business_rules/lib/value-resolver.ts +16 -0
  30. package/src/modules/currencies/commands/currencies.ts +26 -8
  31. package/src/modules/currencies/commands/exchange-rates.ts +26 -8
  32. package/src/modules/currencies/commands/scope.ts +32 -0
  33. package/src/modules/customers/components/detail/ActivitiesSection.tsx +10 -6
  34. package/src/modules/customers/components/detail/ActivityHistorySection.tsx +6 -3
  35. package/src/modules/feature_toggles/api/global/route.ts +7 -1
  36. package/src/modules/feature_toggles/commands/global.ts +9 -12
  37. package/src/modules/query_index/lib/subscriber-scope.ts +98 -14
  38. package/src/modules/query_index/subscribers/delete_one.ts +21 -10
  39. package/src/modules/query_index/subscribers/upsert_one.ts +8 -3
  40. package/src/modules/workflows/AGENTS.md +22 -0
  41. package/src/modules/workflows/api/definitions/[id]/customize/route.ts +9 -0
  42. package/src/modules/workflows/api/definitions/[id]/reset-to-code/route.ts +7 -0
@@ -1,4 +1,4 @@
1
- [build:core] found 3501 entry points
1
+ [build:core] found 3503 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 188 entry points
4
4
  [build:core:generated] built successfully
@@ -10,6 +10,7 @@ function resolveSpecialValue(template, context) {
10
10
  }
11
11
  return getNestedValue(context, path);
12
12
  }
13
+ const DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
13
14
  function getNestedValue(obj, path) {
14
15
  if (!obj || !path) {
15
16
  return void 0;
@@ -21,6 +22,12 @@ function getNestedValue(obj, path) {
21
22
  if (result === null || result === void 0) {
22
23
  return void 0;
23
24
  }
25
+ if (DANGEROUS_KEYS.has(key)) {
26
+ return void 0;
27
+ }
28
+ if (!Object.prototype.hasOwnProperty.call(result, key)) {
29
+ return void 0;
30
+ }
24
31
  result = result[key];
25
32
  }
26
33
  return result;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/business_rules/lib/value-resolver.ts"],
4
- "sourcesContent": ["import type { EvaluationContext } from './expression-evaluator'\n\n/**\n * Shared utilities for resolving values from context\n * Used by both expression-evaluator and action-executor\n */\n\n/**\n * Resolve special template values like {{today}}, {{user.id}}, etc.\n */\nexport function resolveSpecialValue(template: string, context: EvaluationContext): any {\n const path = template.slice(2, -2).trim() // Remove {{ and }}\n\n // Special date/time values\n if (path === 'today') {\n const today = context.today || new Date()\n return today.toISOString().split('T')[0] // YYYY-MM-DD\n }\n\n if (path === 'now') {\n const now = context.now || new Date()\n return now.toISOString()\n }\n\n // Context values (user.id, tenant.id, etc.)\n return getNestedValue(context, path)\n}\n\n/**\n * Get nested value from object using dot notation\n * Supports: 'user.name', 'items[0].quantity', 'data.values[2]'\n */\nexport function getNestedValue(obj: any, path: string): any {\n if (!obj || !path) {\n return undefined\n }\n\n // Handle array notation: convert 'items[0]' to 'items.0'\n const normalizedPath = path.replace(/\\[(\\d+)\\]/g, '.$1')\n\n const keys = normalizedPath.split('.')\n let result = obj\n\n for (const key of keys) {\n if (result === null || result === undefined) {\n return undefined\n }\n\n result = result[key]\n }\n\n return result\n}\n"],
5
- "mappings": "AAUO,SAAS,oBAAoB,UAAkB,SAAiC;AACrF,QAAM,OAAO,SAAS,MAAM,GAAG,EAAE,EAAE,KAAK;AAGxC,MAAI,SAAS,SAAS;AACpB,UAAM,QAAQ,QAAQ,SAAS,oBAAI,KAAK;AACxC,WAAO,MAAM,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,EACzC;AAEA,MAAI,SAAS,OAAO;AAClB,UAAM,MAAM,QAAQ,OAAO,oBAAI,KAAK;AACpC,WAAO,IAAI,YAAY;AAAA,EACzB;AAGA,SAAO,eAAe,SAAS,IAAI;AACrC;AAMO,SAAS,eAAe,KAAU,MAAmB;AAC1D,MAAI,CAAC,OAAO,CAAC,MAAM;AACjB,WAAO;AAAA,EACT;AAGA,QAAM,iBAAiB,KAAK,QAAQ,cAAc,KAAK;AAEvD,QAAM,OAAO,eAAe,MAAM,GAAG;AACrC,MAAI,SAAS;AAEb,aAAW,OAAO,MAAM;AACtB,QAAI,WAAW,QAAQ,WAAW,QAAW;AAC3C,aAAO;AAAA,IACT;AAEA,aAAS,OAAO,GAAG;AAAA,EACrB;AAEA,SAAO;AACT;",
4
+ "sourcesContent": ["import type { EvaluationContext } from './expression-evaluator'\n\n/**\n * Shared utilities for resolving values from context\n * Used by both expression-evaluator and action-executor\n */\n\n/**\n * Resolve special template values like {{today}}, {{user.id}}, etc.\n */\nexport function resolveSpecialValue(template: string, context: EvaluationContext): any {\n const path = template.slice(2, -2).trim() // Remove {{ and }}\n\n // Special date/time values\n if (path === 'today') {\n const today = context.today || new Date()\n return today.toISOString().split('T')[0] // YYYY-MM-DD\n }\n\n if (path === 'now') {\n const now = context.now || new Date()\n return now.toISOString()\n }\n\n // Context values (user.id, tenant.id, etc.)\n return getNestedValue(context, path)\n}\n\n// Field paths come from rule authors, so a segment could name a prototype key.\n// Rejecting these \u2014 and requiring own-property access \u2014 keeps traversal on the\n// caller's own data instead of the prototype chain.\nconst DANGEROUS_KEYS = new Set<string>(['__proto__', 'constructor', 'prototype'])\n\n/**\n * Get nested value from object using dot notation\n * Supports: 'user.name', 'items[0].quantity', 'data.values[2]'\n *\n * Segments naming a prototype key, or resolving to an inherited rather than an\n * own property, yield `undefined`.\n */\nexport function getNestedValue(obj: any, path: string): any {\n if (!obj || !path) {\n return undefined\n }\n\n // Handle array notation: convert 'items[0]' to 'items.0'\n const normalizedPath = path.replace(/\\[(\\d+)\\]/g, '.$1')\n\n const keys = normalizedPath.split('.')\n let result = obj\n\n for (const key of keys) {\n if (result === null || result === undefined) {\n return undefined\n }\n\n if (DANGEROUS_KEYS.has(key)) {\n return undefined\n }\n\n if (!Object.prototype.hasOwnProperty.call(result, key)) {\n return undefined\n }\n\n result = result[key]\n }\n\n return result\n}\n"],
5
+ "mappings": "AAUO,SAAS,oBAAoB,UAAkB,SAAiC;AACrF,QAAM,OAAO,SAAS,MAAM,GAAG,EAAE,EAAE,KAAK;AAGxC,MAAI,SAAS,SAAS;AACpB,UAAM,QAAQ,QAAQ,SAAS,oBAAI,KAAK;AACxC,WAAO,MAAM,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,EACzC;AAEA,MAAI,SAAS,OAAO;AAClB,UAAM,MAAM,QAAQ,OAAO,oBAAI,KAAK;AACpC,WAAO,IAAI,YAAY;AAAA,EACzB;AAGA,SAAO,eAAe,SAAS,IAAI;AACrC;AAKA,MAAM,iBAAiB,oBAAI,IAAY,CAAC,aAAa,eAAe,WAAW,CAAC;AASzE,SAAS,eAAe,KAAU,MAAmB;AAC1D,MAAI,CAAC,OAAO,CAAC,MAAM;AACjB,WAAO;AAAA,EACT;AAGA,QAAM,iBAAiB,KAAK,QAAQ,cAAc,KAAK;AAEvD,QAAM,OAAO,eAAe,MAAM,GAAG;AACrC,MAAI,SAAS;AAEb,aAAW,OAAO,MAAM;AACtB,QAAI,WAAW,QAAQ,WAAW,QAAW;AAC3C,aAAO;AAAA,IACT;AAEA,QAAI,eAAe,IAAI,GAAG,GAAG;AAC3B,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,OAAO,UAAU,eAAe,KAAK,QAAQ,GAAG,GAAG;AACtD,aAAO;AAAA,IACT;AAEA,aAAS,OAAO,GAAG;AAAA,EACrB;AAEA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -11,6 +11,7 @@ import {
11
11
  currencyUpdateSchema,
12
12
  currencyDeleteSchema
13
13
  } from "../data/validators.js";
14
+ import { buildCurrencyCommandWhere, ensureCurrencyCommandScope } from "./scope.js";
14
15
  const currencyCrudEvents = {
15
16
  module: "currencies",
16
17
  entity: "currency",
@@ -21,9 +22,13 @@ const currencyCrudEvents = {
21
22
  tenantId: ctx.identifiers.tenantId
22
23
  })
23
24
  };
24
- async function loadCurrencySnapshot(em, id) {
25
- const record = await em.findOne(Currency, { id });
25
+ async function loadCurrencySnapshot(em, id, ctx) {
26
+ const record = await em.findOne(
27
+ Currency,
28
+ buildCurrencyCommandWhere(ctx, { id })
29
+ );
26
30
  if (!record) return null;
31
+ ensureCurrencyCommandScope(ctx, record);
27
32
  return {
28
33
  id: record.id,
29
34
  organizationId: record.organizationId,
@@ -57,6 +62,7 @@ const createCurrencyCommand = {
57
62
  id: "currencies.currencies.create",
58
63
  async execute(input, ctx) {
59
64
  const parsed = currencyCreateSchema.parse(input);
65
+ ensureCurrencyCommandScope(ctx, parsed);
60
66
  const em = ctx.container.resolve("em").fork();
61
67
  const existing = await em.findOne(Currency, {
62
68
  code: parsed.code,
@@ -113,7 +119,7 @@ const createCurrencyCommand = {
113
119
  },
114
120
  captureAfter: async (_input, result, ctx) => {
115
121
  const em = ctx.container.resolve("em").fork();
116
- return loadCurrencySnapshot(em, result.currencyId);
122
+ return loadCurrencySnapshot(em, result.currencyId, ctx);
117
123
  },
118
124
  buildLog: async ({ snapshots }) => {
119
125
  const after = snapshots.after;
@@ -157,17 +163,21 @@ const updateCurrencyCommand = {
157
163
  async prepare(input, ctx) {
158
164
  requireId(input.id, "Currency ID is required");
159
165
  const em = ctx.container.resolve("em");
160
- const before = await loadCurrencySnapshot(em, input.id);
166
+ const before = await loadCurrencySnapshot(em, input.id, ctx);
161
167
  return { before };
162
168
  },
163
169
  async execute(input, ctx) {
164
170
  const parsed = currencyUpdateSchema.parse(input);
165
171
  requireId(parsed.id, "Currency ID is required");
166
172
  const em = ctx.container.resolve("em").fork();
167
- const record = await em.findOne(Currency, { id: parsed.id, deletedAt: null });
173
+ const record = await em.findOne(
174
+ Currency,
175
+ buildCurrencyCommandWhere(ctx, { id: parsed.id })
176
+ );
168
177
  if (!record) {
169
178
  throw new CrudHttpError(404, { error: "Currency not found" });
170
179
  }
180
+ ensureCurrencyCommandScope(ctx, record);
171
181
  if (parsed.code && parsed.code !== record.code) {
172
182
  const existing = await em.findOne(Currency, {
173
183
  code: parsed.code,
@@ -226,7 +236,7 @@ const updateCurrencyCommand = {
226
236
  },
227
237
  captureAfter: async (_input, result, ctx) => {
228
238
  const em = ctx.container.resolve("em").fork();
229
- return loadCurrencySnapshot(em, result.currencyId);
239
+ return loadCurrencySnapshot(em, result.currencyId, ctx);
230
240
  },
231
241
  buildLog: async ({ snapshots, result }) => {
232
242
  const before = snapshots.before;
@@ -270,17 +280,21 @@ const deleteCurrencyCommand = {
270
280
  async prepare(input, ctx) {
271
281
  requireId(input.id, "Currency ID is required");
272
282
  const em = ctx.container.resolve("em");
273
- const before = await loadCurrencySnapshot(em, input.id);
283
+ const before = await loadCurrencySnapshot(em, input.id, ctx);
274
284
  return { before };
275
285
  },
276
286
  async execute(input, ctx) {
277
287
  const parsed = currencyDeleteSchema.parse(input);
278
288
  requireId(parsed.id, "Currency ID is required");
279
289
  const em = ctx.container.resolve("em").fork();
280
- const record = await em.findOne(Currency, { id: parsed.id, deletedAt: null });
290
+ const record = await em.findOne(
291
+ Currency,
292
+ buildCurrencyCommandWhere(ctx, { id: parsed.id })
293
+ );
281
294
  if (!record) {
282
295
  throw new CrudHttpError(404, { error: "Currency not found" });
283
296
  }
297
+ ensureCurrencyCommandScope(ctx, record);
284
298
  if (record.isBase) {
285
299
  throw new CrudHttpError(400, { error: "Cannot delete the base currency" });
286
300
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/currencies/commands/currencies.ts"],
4
- "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { buildChanges, requireId, emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, conflict, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { Currency, ExchangeRate } from '../data/entities'\nimport {\n currencyCreateSchema,\n currencyUpdateSchema,\n currencyDeleteSchema,\n type CurrencyCreateInput,\n type CurrencyUpdateInput,\n type CurrencyDeleteInput,\n} from '../data/validators'\nimport type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\n\nconst currencyCrudEvents: CrudEventsConfig = {\n module: 'currencies',\n entity: 'currency',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\ntype CurrencySnapshot = {\n id: string\n organizationId: string\n tenantId: string\n code: string\n name: string\n symbol: string | null\n decimalPlaces: number\n thousandsSeparator: string | null\n decimalSeparator: string | null\n isBase: boolean\n isActive: boolean\n createdAt: string\n updatedAt: string\n}\n\ntype CurrencyUndoPayload = UndoPayload<CurrencySnapshot>\n\nasync function loadCurrencySnapshot(em: EntityManager, id: string): Promise<CurrencySnapshot | null> {\n const record = await em.findOne(Currency, { id })\n if (!record) return null\n return {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n code: record.code,\n name: record.name,\n symbol: record.symbol ?? null,\n decimalPlaces: record.decimalPlaces,\n thousandsSeparator: record.thousandsSeparator ?? null,\n decimalSeparator: record.decimalSeparator ?? null,\n isBase: !!record.isBase,\n isActive: !!record.isActive,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt.toISOString(),\n }\n}\n\nasync function enforceBaseCurrency(\n em: EntityManager,\n currencyId: string,\n organizationId: string,\n tenantId: string\n): Promise<void> {\n await em.nativeUpdate(\n Currency,\n {\n organizationId,\n tenantId,\n id: { $ne: currencyId },\n isBase: true,\n deletedAt: null,\n },\n { isBase: false, updatedAt: new Date() }\n )\n}\n\nconst createCurrencyCommand: CommandHandler<CurrencyCreateInput, { currencyId: string }> = {\n id: 'currencies.currencies.create',\n async execute(input, ctx) {\n const parsed = currencyCreateSchema.parse(input)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n // Check for duplicate code\n const existing = await em.findOne(Currency, {\n code: parsed.code,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n deletedAt: null,\n })\n if (existing) {\n throw conflict('Currency code already exists for this organization.')\n }\n\n const now = new Date()\n const record = em.create(Currency, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n code: parsed.code,\n name: parsed.name,\n symbol: parsed.symbol ?? null,\n decimalPlaces: parsed.decimalPlaces ?? 2,\n thousandsSeparator: parsed.thousandsSeparator ?? null,\n decimalSeparator: parsed.decimalSeparator ?? null,\n isBase: parsed.isBase ?? false,\n isActive: parsed.isActive !== false,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(record)\n\n // Demote any existing base currency and insert the new record in one\n // transaction; a partial commit would leave zero or two base currencies.\n try {\n await withAtomicFlush(\n em,\n [\n () =>\n record.isBase\n ? enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId)\n : undefined,\n ],\n { transaction: true },\n )\n } catch (err) {\n if (isUniqueViolation(err, 'currencies_code_scope_unique')) {\n throw conflict('Currency code already exists for this organization.')\n }\n throw err\n }\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: currencyCrudEvents,\n })\n\n return { currencyId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadCurrencySnapshot(em, result.currencyId)\n },\n buildLog: async ({ snapshots }) => {\n const after = snapshots.after as CurrencySnapshot | undefined\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.audit.create', 'Create currency'),\n resourceKind: 'currencies.currency',\n resourceId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n snapshotAfter: after,\n payload: { undo: { after } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CurrencyUndoPayload>(logEntry)\n const after = payload?.after ?? null\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(Currency, { id: after.id })\n if (!record) return\n record.deletedAt = new Date()\n record.isActive = false\n await em.flush()\n },\n redo: makeCreateRedo<Currency, CurrencySnapshot, CurrencyCreateInput, { currencyId: string }>({\n entityClass: Currency,\n buildResult: (entity) => ({ currencyId: entity.id }),\n events: currencyCrudEvents,\n afterRestore: async ({ em, entity }) => {\n if (entity.isBase) {\n await enforceBaseCurrency(em, entity.id, entity.organizationId, entity.tenantId)\n await em.flush()\n }\n },\n }),\n}\n\nconst updateCurrencyCommand: CommandHandler<CurrencyUpdateInput, { currencyId: string }> = {\n id: 'currencies.currencies.update',\n async prepare(input, ctx) {\n requireId(input.id, 'Currency ID is required')\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadCurrencySnapshot(em, input.id)\n return { before }\n },\n async execute(input, ctx) {\n const parsed = currencyUpdateSchema.parse(input)\n requireId(parsed.id, 'Currency ID is required')\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(Currency, { id: parsed.id, deletedAt: null })\n if (!record) {\n throw new CrudHttpError(404, { error: 'Currency not found' })\n }\n\n // Check code uniqueness if changing code\n if (parsed.code && parsed.code !== record.code) {\n const existing = await em.findOne(Currency, {\n code: parsed.code,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n id: { $ne: record.id },\n deletedAt: null,\n })\n if (existing) {\n throw conflict('Currency code already exists for this organization.')\n }\n }\n\n const allChanges = buildChanges(record as unknown as Record<string, unknown>, parsed, [\n 'code',\n 'name',\n 'symbol',\n 'decimalPlaces',\n 'thousandsSeparator',\n 'decimalSeparator',\n 'isBase',\n 'isActive',\n ])\n const changes = Object.fromEntries(\n Object.entries(allChanges).filter(([, c]) => c.to !== undefined),\n ) as Record<string, { from: unknown; to: unknown }>\n\n if (Object.keys(changes).length === 0) {\n return { currencyId: record.id }\n }\n\n // Demote any existing base currency and persist the scalar changes in one\n // transaction; a partial commit would leave zero or two base currencies.\n // The scalar mutations live in the first phase so the per-phase flush issues\n // the pending changeset on the managed `record` before `enforceBaseCurrency`\n // runs its interleaved `nativeUpdate` (which would otherwise drop the pending\n // UPDATE under v7).\n await withAtomicFlush(\n em,\n [\n () => {\n for (const [key, change] of Object.entries(changes)) {\n ;(record as any)[key] = change.to\n }\n record.updatedAt = new Date()\n },\n () =>\n parsed.isBase === true && record.isBase\n ? enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId)\n : undefined,\n ],\n { transaction: true },\n )\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: currencyCrudEvents,\n })\n\n return { currencyId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadCurrencySnapshot(em, result.currencyId)\n },\n buildLog: async ({ snapshots, result }) => {\n const before = snapshots.before as CurrencySnapshot | undefined\n const after = snapshots.after as CurrencySnapshot | undefined\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.audit.update', 'Update currency'),\n resourceKind: 'currencies.currency',\n resourceId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n snapshotBefore: before ?? undefined,\n snapshotAfter: after,\n payload: { undo: { before, after } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CurrencyUndoPayload>(logEntry)\n const before = payload?.before ?? null\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(Currency, { id: before.id })\n if (!record) return\n Object.assign(record, {\n code: before.code,\n name: before.name,\n symbol: before.symbol,\n decimalPlaces: before.decimalPlaces,\n thousandsSeparator: before.thousandsSeparator,\n decimalSeparator: before.decimalSeparator,\n isBase: before.isBase,\n isActive: before.isActive,\n updatedAt: new Date(),\n })\n await em.flush()\n },\n}\n\nconst deleteCurrencyCommand: CommandHandler<CurrencyDeleteInput, { currencyId: string }> = {\n id: 'currencies.currencies.delete',\n async prepare(input, ctx) {\n requireId(input.id, 'Currency ID is required')\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadCurrencySnapshot(em, input.id)\n return { before }\n },\n async execute(input, ctx) {\n const parsed = currencyDeleteSchema.parse(input)\n requireId(parsed.id, 'Currency ID is required')\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(Currency, { id: parsed.id, deletedAt: null })\n if (!record) {\n throw new CrudHttpError(404, { error: 'Currency not found' })\n }\n\n // Prevent deleting base currency\n if (record.isBase) {\n throw new CrudHttpError(400, { error: 'Cannot delete the base currency' })\n }\n\n // Prevent deleting currency with active exchange rates\n const activeRatesCount = await em.count(ExchangeRate, {\n $or: [\n { fromCurrencyCode: record.code },\n { toCurrencyCode: record.code },\n ],\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n deletedAt: null,\n isActive: true,\n })\n \n if (activeRatesCount > 0) {\n throw new CrudHttpError(400, { \n error: `Cannot delete currency ${record.code} because it has ${activeRatesCount} active exchange rate(s). Please delete or deactivate the exchange rates first.` \n })\n }\n\n record.deletedAt = new Date()\n record.isActive = false\n await em.flush()\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: currencyCrudEvents,\n })\n\n return { currencyId: record.id }\n },\n buildLog: async ({ snapshots, result }) => {\n const before = snapshots.before as CurrencySnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.audit.delete', 'Delete currency'),\n resourceKind: 'currencies.currency',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: { undo: { before } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CurrencyUndoPayload>(logEntry)\n const before = payload?.before ?? null\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(Currency, { id: before.id })\n if (!record) return\n record.deletedAt = null\n record.isActive = before.isActive\n record.updatedAt = new Date()\n await em.flush()\n },\n}\n\nregisterCommand(createCurrencyCommand)\nregisterCommand(updateCurrencyCommand)\nregisterCommand(deleteCurrencyCommand)\n\nexport { createCurrencyCommand, updateCurrencyCommand, deleteCurrencyCommand }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { buildChanges, requireId, emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, conflict, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { Currency, ExchangeRate } from '../data/entities'\nimport {\n currencyCreateSchema,\n currencyUpdateSchema,\n currencyDeleteSchema,\n type CurrencyCreateInput,\n type CurrencyUpdateInput,\n type CurrencyDeleteInput,\n} from '../data/validators'\nimport type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { buildCurrencyCommandWhere, ensureCurrencyCommandScope } from './scope'\n\nconst currencyCrudEvents: CrudEventsConfig = {\n module: 'currencies',\n entity: 'currency',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\ntype CurrencySnapshot = {\n id: string\n organizationId: string\n tenantId: string\n code: string\n name: string\n symbol: string | null\n decimalPlaces: number\n thousandsSeparator: string | null\n decimalSeparator: string | null\n isBase: boolean\n isActive: boolean\n createdAt: string\n updatedAt: string\n}\n\ntype CurrencyUndoPayload = UndoPayload<CurrencySnapshot>\n\nasync function loadCurrencySnapshot(\n em: EntityManager,\n id: string,\n ctx: Parameters<typeof buildCurrencyCommandWhere>[0],\n): Promise<CurrencySnapshot | null> {\n const record = await em.findOne(\n Currency,\n buildCurrencyCommandWhere<Currency>(ctx, { id }),\n )\n if (!record) return null\n ensureCurrencyCommandScope(ctx, record)\n return {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n code: record.code,\n name: record.name,\n symbol: record.symbol ?? null,\n decimalPlaces: record.decimalPlaces,\n thousandsSeparator: record.thousandsSeparator ?? null,\n decimalSeparator: record.decimalSeparator ?? null,\n isBase: !!record.isBase,\n isActive: !!record.isActive,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt.toISOString(),\n }\n}\n\nasync function enforceBaseCurrency(\n em: EntityManager,\n currencyId: string,\n organizationId: string,\n tenantId: string\n): Promise<void> {\n await em.nativeUpdate(\n Currency,\n {\n organizationId,\n tenantId,\n id: { $ne: currencyId },\n isBase: true,\n deletedAt: null,\n },\n { isBase: false, updatedAt: new Date() }\n )\n}\n\nconst createCurrencyCommand: CommandHandler<CurrencyCreateInput, { currencyId: string }> = {\n id: 'currencies.currencies.create',\n async execute(input, ctx) {\n const parsed = currencyCreateSchema.parse(input)\n ensureCurrencyCommandScope(ctx, parsed)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n // Check for duplicate code\n const existing = await em.findOne(Currency, {\n code: parsed.code,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n deletedAt: null,\n })\n if (existing) {\n throw conflict('Currency code already exists for this organization.')\n }\n\n const now = new Date()\n const record = em.create(Currency, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n code: parsed.code,\n name: parsed.name,\n symbol: parsed.symbol ?? null,\n decimalPlaces: parsed.decimalPlaces ?? 2,\n thousandsSeparator: parsed.thousandsSeparator ?? null,\n decimalSeparator: parsed.decimalSeparator ?? null,\n isBase: parsed.isBase ?? false,\n isActive: parsed.isActive !== false,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(record)\n\n // Demote any existing base currency and insert the new record in one\n // transaction; a partial commit would leave zero or two base currencies.\n try {\n await withAtomicFlush(\n em,\n [\n () =>\n record.isBase\n ? enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId)\n : undefined,\n ],\n { transaction: true },\n )\n } catch (err) {\n if (isUniqueViolation(err, 'currencies_code_scope_unique')) {\n throw conflict('Currency code already exists for this organization.')\n }\n throw err\n }\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: currencyCrudEvents,\n })\n\n return { currencyId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadCurrencySnapshot(em, result.currencyId, ctx)\n },\n buildLog: async ({ snapshots }) => {\n const after = snapshots.after as CurrencySnapshot | undefined\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.audit.create', 'Create currency'),\n resourceKind: 'currencies.currency',\n resourceId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n snapshotAfter: after,\n payload: { undo: { after } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CurrencyUndoPayload>(logEntry)\n const after = payload?.after ?? null\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(Currency, { id: after.id })\n if (!record) return\n record.deletedAt = new Date()\n record.isActive = false\n await em.flush()\n },\n redo: makeCreateRedo<Currency, CurrencySnapshot, CurrencyCreateInput, { currencyId: string }>({\n entityClass: Currency,\n buildResult: (entity) => ({ currencyId: entity.id }),\n events: currencyCrudEvents,\n afterRestore: async ({ em, entity }) => {\n if (entity.isBase) {\n await enforceBaseCurrency(em, entity.id, entity.organizationId, entity.tenantId)\n await em.flush()\n }\n },\n }),\n}\n\nconst updateCurrencyCommand: CommandHandler<CurrencyUpdateInput, { currencyId: string }> = {\n id: 'currencies.currencies.update',\n async prepare(input, ctx) {\n requireId(input.id, 'Currency ID is required')\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadCurrencySnapshot(em, input.id, ctx)\n return { before }\n },\n async execute(input, ctx) {\n const parsed = currencyUpdateSchema.parse(input)\n requireId(parsed.id, 'Currency ID is required')\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(\n Currency,\n buildCurrencyCommandWhere<Currency>(ctx, { id: parsed.id }),\n )\n if (!record) {\n throw new CrudHttpError(404, { error: 'Currency not found' })\n }\n ensureCurrencyCommandScope(ctx, record)\n\n // Check code uniqueness if changing code\n if (parsed.code && parsed.code !== record.code) {\n const existing = await em.findOne(Currency, {\n code: parsed.code,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n id: { $ne: record.id },\n deletedAt: null,\n })\n if (existing) {\n throw conflict('Currency code already exists for this organization.')\n }\n }\n\n const allChanges = buildChanges(record as unknown as Record<string, unknown>, parsed, [\n 'code',\n 'name',\n 'symbol',\n 'decimalPlaces',\n 'thousandsSeparator',\n 'decimalSeparator',\n 'isBase',\n 'isActive',\n ])\n const changes = Object.fromEntries(\n Object.entries(allChanges).filter(([, c]) => c.to !== undefined),\n ) as Record<string, { from: unknown; to: unknown }>\n\n if (Object.keys(changes).length === 0) {\n return { currencyId: record.id }\n }\n\n // Demote any existing base currency and persist the scalar changes in one\n // transaction; a partial commit would leave zero or two base currencies.\n // The scalar mutations live in the first phase so the per-phase flush issues\n // the pending changeset on the managed `record` before `enforceBaseCurrency`\n // runs its interleaved `nativeUpdate` (which would otherwise drop the pending\n // UPDATE under v7).\n await withAtomicFlush(\n em,\n [\n () => {\n for (const [key, change] of Object.entries(changes)) {\n ;(record as any)[key] = change.to\n }\n record.updatedAt = new Date()\n },\n () =>\n parsed.isBase === true && record.isBase\n ? enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId)\n : undefined,\n ],\n { transaction: true },\n )\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: currencyCrudEvents,\n })\n\n return { currencyId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadCurrencySnapshot(em, result.currencyId, ctx)\n },\n buildLog: async ({ snapshots, result }) => {\n const before = snapshots.before as CurrencySnapshot | undefined\n const after = snapshots.after as CurrencySnapshot | undefined\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.audit.update', 'Update currency'),\n resourceKind: 'currencies.currency',\n resourceId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n snapshotBefore: before ?? undefined,\n snapshotAfter: after,\n payload: { undo: { before, after } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CurrencyUndoPayload>(logEntry)\n const before = payload?.before ?? null\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(Currency, { id: before.id })\n if (!record) return\n Object.assign(record, {\n code: before.code,\n name: before.name,\n symbol: before.symbol,\n decimalPlaces: before.decimalPlaces,\n thousandsSeparator: before.thousandsSeparator,\n decimalSeparator: before.decimalSeparator,\n isBase: before.isBase,\n isActive: before.isActive,\n updatedAt: new Date(),\n })\n await em.flush()\n },\n}\n\nconst deleteCurrencyCommand: CommandHandler<CurrencyDeleteInput, { currencyId: string }> = {\n id: 'currencies.currencies.delete',\n async prepare(input, ctx) {\n requireId(input.id, 'Currency ID is required')\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadCurrencySnapshot(em, input.id, ctx)\n return { before }\n },\n async execute(input, ctx) {\n const parsed = currencyDeleteSchema.parse(input)\n requireId(parsed.id, 'Currency ID is required')\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(\n Currency,\n buildCurrencyCommandWhere<Currency>(ctx, { id: parsed.id }),\n )\n if (!record) {\n throw new CrudHttpError(404, { error: 'Currency not found' })\n }\n ensureCurrencyCommandScope(ctx, record)\n\n // Prevent deleting base currency\n if (record.isBase) {\n throw new CrudHttpError(400, { error: 'Cannot delete the base currency' })\n }\n\n // Prevent deleting currency with active exchange rates\n const activeRatesCount = await em.count(ExchangeRate, {\n $or: [\n { fromCurrencyCode: record.code },\n { toCurrencyCode: record.code },\n ],\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n deletedAt: null,\n isActive: true,\n })\n \n if (activeRatesCount > 0) {\n throw new CrudHttpError(400, { \n error: `Cannot delete currency ${record.code} because it has ${activeRatesCount} active exchange rate(s). Please delete or deactivate the exchange rates first.` \n })\n }\n\n record.deletedAt = new Date()\n record.isActive = false\n await em.flush()\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: currencyCrudEvents,\n })\n\n return { currencyId: record.id }\n },\n buildLog: async ({ snapshots, result }) => {\n const before = snapshots.before as CurrencySnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.audit.delete', 'Delete currency'),\n resourceKind: 'currencies.currency',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: { undo: { before } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CurrencyUndoPayload>(logEntry)\n const before = payload?.before ?? null\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(Currency, { id: before.id })\n if (!record) return\n record.deletedAt = null\n record.isActive = before.isActive\n record.updatedAt = new Date()\n await em.flush()\n },\n}\n\nregisterCommand(createCurrencyCommand)\nregisterCommand(updateCurrencyCommand)\nregisterCommand(deleteCurrencyCommand)\n\nexport { createCurrencyCommand, updateCurrencyCommand, deleteCurrencyCommand }\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -10,6 +10,7 @@ import {
10
10
  exchangeRateUpdateSchema,
11
11
  exchangeRateDeleteSchema
12
12
  } from "../data/validators.js";
13
+ import { buildCurrencyCommandWhere, ensureCurrencyCommandScope } from "./scope.js";
13
14
  const exchangeRateCrudEvents = {
14
15
  module: "currencies",
15
16
  entity: "exchange_rate",
@@ -20,9 +21,13 @@ const exchangeRateCrudEvents = {
20
21
  tenantId: ctx.identifiers.tenantId
21
22
  })
22
23
  };
23
- async function loadExchangeRateSnapshot(em, id) {
24
- const record = await em.findOne(ExchangeRate, { id });
24
+ async function loadExchangeRateSnapshot(em, id, ctx) {
25
+ const record = await em.findOne(
26
+ ExchangeRate,
27
+ buildCurrencyCommandWhere(ctx, { id })
28
+ );
25
29
  if (!record) return null;
30
+ ensureCurrencyCommandScope(ctx, record);
26
31
  return {
27
32
  id: record.id,
28
33
  organizationId: record.organizationId,
@@ -62,6 +67,7 @@ const createExchangeRateCommand = {
62
67
  id: "currencies.exchange_rates.create",
63
68
  async execute(input, ctx) {
64
69
  const parsed = exchangeRateCreateSchema.parse(input);
70
+ ensureCurrencyCommandScope(ctx, parsed);
65
71
  const em = ctx.container.resolve("em").fork();
66
72
  await validateCurrenciesExist(em, parsed.fromCurrencyCode, parsed.toCurrencyCode, parsed.organizationId, parsed.tenantId);
67
73
  const existing = await em.findOne(ExchangeRate, {
@@ -115,7 +121,7 @@ const createExchangeRateCommand = {
115
121
  },
116
122
  captureAfter: async (_input, result, ctx) => {
117
123
  const em = ctx.container.resolve("em").fork();
118
- return loadExchangeRateSnapshot(em, result.exchangeRateId);
124
+ return loadExchangeRateSnapshot(em, result.exchangeRateId, ctx);
119
125
  },
120
126
  buildLog: async ({ snapshots }) => {
121
127
  const after = snapshots.after;
@@ -154,17 +160,21 @@ const updateExchangeRateCommand = {
154
160
  async prepare(input, ctx) {
155
161
  requireId(input.id, "Exchange rate ID is required");
156
162
  const em = ctx.container.resolve("em");
157
- const before = await loadExchangeRateSnapshot(em, input.id);
163
+ const before = await loadExchangeRateSnapshot(em, input.id, ctx);
158
164
  return { before };
159
165
  },
160
166
  async execute(input, ctx) {
161
167
  const parsed = exchangeRateUpdateSchema.parse(input);
162
168
  requireId(parsed.id, "Exchange rate ID is required");
163
169
  const em = ctx.container.resolve("em").fork();
164
- const record = await em.findOne(ExchangeRate, { id: parsed.id, deletedAt: null });
170
+ const record = await em.findOne(
171
+ ExchangeRate,
172
+ buildCurrencyCommandWhere(ctx, { id: parsed.id })
173
+ );
165
174
  if (!record) {
166
175
  throw new CrudHttpError(404, { error: "Exchange rate not found" });
167
176
  }
177
+ ensureCurrencyCommandScope(ctx, record);
168
178
  const fromCode = parsed.fromCurrencyCode ?? record.fromCurrencyCode;
169
179
  const toCode = parsed.toCurrencyCode ?? record.toCurrencyCode;
170
180
  if (parsed.fromCurrencyCode || parsed.toCurrencyCode) {
@@ -231,7 +241,7 @@ const updateExchangeRateCommand = {
231
241
  },
232
242
  captureAfter: async (_input, result, ctx) => {
233
243
  const em = ctx.container.resolve("em").fork();
234
- return loadExchangeRateSnapshot(em, result.exchangeRateId);
244
+ return loadExchangeRateSnapshot(em, result.exchangeRateId, ctx);
235
245
  },
236
246
  buildLog: async ({ snapshots, result }) => {
237
247
  const before = snapshots.before;
@@ -274,17 +284,21 @@ const deleteExchangeRateCommand = {
274
284
  async prepare(input, ctx) {
275
285
  requireId(input.id, "Exchange rate ID is required");
276
286
  const em = ctx.container.resolve("em");
277
- const before = await loadExchangeRateSnapshot(em, input.id);
287
+ const before = await loadExchangeRateSnapshot(em, input.id, ctx);
278
288
  return { before };
279
289
  },
280
290
  async execute(input, ctx) {
281
291
  const parsed = exchangeRateDeleteSchema.parse(input);
282
292
  requireId(parsed.id, "Exchange rate ID is required");
283
293
  const em = ctx.container.resolve("em").fork();
284
- const record = await em.findOne(ExchangeRate, { id: parsed.id, deletedAt: null });
294
+ const record = await em.findOne(
295
+ ExchangeRate,
296
+ buildCurrencyCommandWhere(ctx, { id: parsed.id })
297
+ );
285
298
  if (!record) {
286
299
  throw new CrudHttpError(404, { error: "Exchange rate not found" });
287
300
  }
301
+ ensureCurrencyCommandScope(ctx, record);
288
302
  record.deletedAt = /* @__PURE__ */ new Date();
289
303
  record.isActive = false;
290
304
  await em.flush();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/currencies/commands/exchange-rates.ts"],
4
- "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { buildChanges, requireId, emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, conflict, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { ExchangeRate, Currency } from '../data/entities'\nimport {\n exchangeRateCreateSchema,\n exchangeRateUpdateSchema,\n exchangeRateDeleteSchema,\n type ExchangeRateCreateInput,\n type ExchangeRateUpdateInput,\n type ExchangeRateDeleteInput,\n} from '../data/validators'\nimport type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\n\nconst exchangeRateCrudEvents: CrudEventsConfig = {\n module: 'currencies',\n entity: 'exchange_rate',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\ntype ExchangeRateSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n fromCurrencyCode: string\n toCurrencyCode: string\n rate: string\n date: string\n source: string\n type: string | null\n isActive: boolean\n createdAt: string\n updatedAt: string\n}\n\ntype ExchangeRateUndoPayload = UndoPayload<ExchangeRateSnapshot>\n\nasync function loadExchangeRateSnapshot(em: EntityManager, id: string): Promise<ExchangeRateSnapshot | null> {\n const record = await em.findOne(ExchangeRate, { id })\n if (!record) return null\n return {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n fromCurrencyCode: record.fromCurrencyCode,\n toCurrencyCode: record.toCurrencyCode,\n rate: record.rate,\n date: record.date.toISOString(),\n source: record.source,\n type: record.type ?? null,\n isActive: !!record.isActive,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt.toISOString(),\n }\n}\n\nasync function validateCurrenciesExist(\n em: EntityManager,\n fromCode: string,\n toCode: string,\n organizationId: string,\n tenantId: string\n): Promise<void> {\n const fromCurrency = await em.findOne(Currency, {\n code: fromCode,\n organizationId,\n tenantId,\n deletedAt: null,\n })\n if (!fromCurrency) {\n throw new CrudHttpError(400, { error: `From currency ${fromCode} does not exist or is inactive` })\n }\n\n const toCurrency = await em.findOne(Currency, {\n code: toCode,\n organizationId,\n tenantId,\n deletedAt: null,\n })\n if (!toCurrency) {\n throw new CrudHttpError(400, { error: `To currency ${toCode} does not exist or is inactive` })\n }\n}\n\nconst createExchangeRateCommand: CommandHandler<ExchangeRateCreateInput, { exchangeRateId: string }> = {\n id: 'currencies.exchange_rates.create',\n async execute(input, ctx) {\n const parsed = exchangeRateCreateSchema.parse(input)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n // Validate currencies exist\n await validateCurrenciesExist(em, parsed.fromCurrencyCode, parsed.toCurrencyCode, parsed.organizationId, parsed.tenantId)\n\n // Check for duplicate rate (same pair + date + source)\n const existing = await em.findOne(ExchangeRate, {\n fromCurrencyCode: parsed.fromCurrencyCode,\n toCurrencyCode: parsed.toCurrencyCode,\n date: parsed.date,\n source: parsed.source,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n deletedAt: null,\n })\n if (existing) {\n throw conflict('Exchange rate for this currency pair, date, and source already exists')\n }\n\n const now = new Date()\n const record = em.create(ExchangeRate, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n fromCurrencyCode: parsed.fromCurrencyCode,\n toCurrencyCode: parsed.toCurrencyCode,\n rate: parsed.rate,\n date: parsed.date,\n source: parsed.source,\n type: parsed.type ?? null,\n isActive: parsed.isActive !== false,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(record)\n try {\n await em.flush()\n } catch (err) {\n if (isUniqueViolation(err, 'exchange_rates_pair_datetime_source_unique')) {\n throw conflict('Exchange rate for this currency pair, date, and source already exists')\n }\n throw err\n }\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: exchangeRateCrudEvents,\n })\n\n return { exchangeRateId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadExchangeRateSnapshot(em, result.exchangeRateId)\n },\n buildLog: async ({ snapshots }) => {\n const after = snapshots.after as ExchangeRateSnapshot | undefined\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.rates.audit.create', 'Create exchange rate'),\n resourceKind: 'currencies.exchange_rate',\n resourceId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n snapshotAfter: after,\n payload: { undo: { after } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ExchangeRateUndoPayload>(logEntry)\n const after = payload?.after ?? null\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(ExchangeRate, { id: after.id })\n if (!record) return\n record.deletedAt = new Date()\n record.isActive = false\n await em.flush()\n },\n redo: makeCreateRedo<ExchangeRate, ExchangeRateSnapshot, ExchangeRateCreateInput, { exchangeRateId: string }>({\n entityClass: ExchangeRate,\n dateFields: ['createdAt', 'updatedAt', 'deletedAt', 'date'],\n buildResult: (entity) => ({ exchangeRateId: entity.id }),\n events: exchangeRateCrudEvents,\n }),\n}\n\nconst updateExchangeRateCommand: CommandHandler<ExchangeRateUpdateInput, { exchangeRateId: string }> = {\n id: 'currencies.exchange_rates.update',\n async prepare(input, ctx) {\n requireId(input.id, 'Exchange rate ID is required')\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadExchangeRateSnapshot(em, input.id)\n return { before }\n },\n async execute(input, ctx) {\n const parsed = exchangeRateUpdateSchema.parse(input)\n requireId(parsed.id, 'Exchange rate ID is required')\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(ExchangeRate, { id: parsed.id, deletedAt: null })\n if (!record) {\n throw new CrudHttpError(404, { error: 'Exchange rate not found' })\n }\n\n // Validate currencies if changed\n const fromCode = parsed.fromCurrencyCode ?? record.fromCurrencyCode\n const toCode = parsed.toCurrencyCode ?? record.toCurrencyCode\n if (parsed.fromCurrencyCode || parsed.toCurrencyCode) {\n await validateCurrenciesExist(em, fromCode, toCode, record.organizationId, record.tenantId)\n }\n\n // Check for duplicate if changing pair, date, or source\n if (parsed.fromCurrencyCode || parsed.toCurrencyCode || parsed.date || parsed.source) {\n const date = parsed.date ?? record.date\n const source = parsed.source ?? record.source\n const existing = await em.findOne(ExchangeRate, {\n fromCurrencyCode: fromCode,\n toCurrencyCode: toCode,\n date,\n source,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n id: { $ne: record.id },\n deletedAt: null,\n })\n if (existing) {\n throw conflict('Exchange rate for this currency pair, date, and source already exists')\n }\n }\n\n const allChanges = buildChanges(record as unknown as Record<string, unknown>, parsed, [\n 'fromCurrencyCode',\n 'toCurrencyCode',\n 'rate',\n 'date',\n 'source',\n 'type',\n 'isActive',\n ])\n const changes = Object.fromEntries(\n Object.entries(allChanges).filter(([, c]) => c.to !== undefined),\n ) as Record<string, { from: unknown; to: unknown }>\n\n if (Object.keys(changes).length === 0) {\n return { exchangeRateId: record.id }\n }\n\n for (const [key, change] of Object.entries(changes)) {\n ;(record as any)[key] = change.to\n }\n record.updatedAt = new Date()\n \n // Validate final state after merging changes\n if (record.fromCurrencyCode === record.toCurrencyCode) {\n throw new CrudHttpError(400, { error: 'From and To currencies must be different' })\n }\n \n const rateValue = parseFloat(record.rate)\n if (isNaN(rateValue) || rateValue <= 0) {\n throw new CrudHttpError(400, { error: 'Rate must be greater than zero' })\n }\n \n await em.flush()\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: exchangeRateCrudEvents,\n })\n\n return { exchangeRateId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadExchangeRateSnapshot(em, result.exchangeRateId)\n },\n buildLog: async ({ snapshots, result }) => {\n const before = snapshots.before as ExchangeRateSnapshot | undefined\n const after = snapshots.after as ExchangeRateSnapshot | undefined\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.rates.audit.update', 'Update exchange rate'),\n resourceKind: 'currencies.exchange_rate',\n resourceId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n snapshotBefore: before ?? undefined,\n snapshotAfter: after,\n payload: { undo: { before, after } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ExchangeRateUndoPayload>(logEntry)\n const before = payload?.before ?? null\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(ExchangeRate, { id: before.id })\n if (!record) return\n Object.assign(record, {\n fromCurrencyCode: before.fromCurrencyCode,\n toCurrencyCode: before.toCurrencyCode,\n rate: before.rate,\n date: new Date(before.date),\n source: before.source,\n type: before.type,\n isActive: before.isActive,\n updatedAt: new Date(),\n })\n await em.flush()\n },\n}\n\nconst deleteExchangeRateCommand: CommandHandler<ExchangeRateDeleteInput, { exchangeRateId: string }> = {\n id: 'currencies.exchange_rates.delete',\n async prepare(input, ctx) {\n requireId(input.id, 'Exchange rate ID is required')\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadExchangeRateSnapshot(em, input.id)\n return { before }\n },\n async execute(input, ctx) {\n const parsed = exchangeRateDeleteSchema.parse(input)\n requireId(parsed.id, 'Exchange rate ID is required')\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(ExchangeRate, { id: parsed.id, deletedAt: null })\n if (!record) {\n throw new CrudHttpError(404, { error: 'Exchange rate not found' })\n }\n\n record.deletedAt = new Date()\n record.isActive = false\n await em.flush()\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: exchangeRateCrudEvents,\n })\n\n return { exchangeRateId: record.id }\n },\n buildLog: async ({ snapshots, result }) => {\n const before = snapshots.before as ExchangeRateSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.rates.audit.delete', 'Delete exchange rate'),\n resourceKind: 'currencies.exchange_rate',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: { undo: { before } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ExchangeRateUndoPayload>(logEntry)\n const before = payload?.before ?? null\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(ExchangeRate, { id: before.id })\n if (!record) return\n record.deletedAt = null\n record.isActive = before.isActive\n record.updatedAt = new Date()\n await em.flush()\n },\n}\n\nregisterCommand(createExchangeRateCommand)\nregisterCommand(updateExchangeRateCommand)\nregisterCommand(deleteExchangeRateCommand)\n\nexport { createExchangeRateCommand, updateExchangeRateCommand, deleteExchangeRateCommand }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { buildChanges, requireId, emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, conflict, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { ExchangeRate, Currency } from '../data/entities'\nimport {\n exchangeRateCreateSchema,\n exchangeRateUpdateSchema,\n exchangeRateDeleteSchema,\n type ExchangeRateCreateInput,\n type ExchangeRateUpdateInput,\n type ExchangeRateDeleteInput,\n} from '../data/validators'\nimport type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { buildCurrencyCommandWhere, ensureCurrencyCommandScope } from './scope'\n\nconst exchangeRateCrudEvents: CrudEventsConfig = {\n module: 'currencies',\n entity: 'exchange_rate',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\ntype ExchangeRateSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n fromCurrencyCode: string\n toCurrencyCode: string\n rate: string\n date: string\n source: string\n type: string | null\n isActive: boolean\n createdAt: string\n updatedAt: string\n}\n\ntype ExchangeRateUndoPayload = UndoPayload<ExchangeRateSnapshot>\n\nasync function loadExchangeRateSnapshot(\n em: EntityManager,\n id: string,\n ctx: Parameters<typeof buildCurrencyCommandWhere>[0],\n): Promise<ExchangeRateSnapshot | null> {\n const record = await em.findOne(\n ExchangeRate,\n buildCurrencyCommandWhere<ExchangeRate>(ctx, { id }),\n )\n if (!record) return null\n ensureCurrencyCommandScope(ctx, record)\n return {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n fromCurrencyCode: record.fromCurrencyCode,\n toCurrencyCode: record.toCurrencyCode,\n rate: record.rate,\n date: record.date.toISOString(),\n source: record.source,\n type: record.type ?? null,\n isActive: !!record.isActive,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt.toISOString(),\n }\n}\n\nasync function validateCurrenciesExist(\n em: EntityManager,\n fromCode: string,\n toCode: string,\n organizationId: string,\n tenantId: string\n): Promise<void> {\n const fromCurrency = await em.findOne(Currency, {\n code: fromCode,\n organizationId,\n tenantId,\n deletedAt: null,\n })\n if (!fromCurrency) {\n throw new CrudHttpError(400, { error: `From currency ${fromCode} does not exist or is inactive` })\n }\n\n const toCurrency = await em.findOne(Currency, {\n code: toCode,\n organizationId,\n tenantId,\n deletedAt: null,\n })\n if (!toCurrency) {\n throw new CrudHttpError(400, { error: `To currency ${toCode} does not exist or is inactive` })\n }\n}\n\nconst createExchangeRateCommand: CommandHandler<ExchangeRateCreateInput, { exchangeRateId: string }> = {\n id: 'currencies.exchange_rates.create',\n async execute(input, ctx) {\n const parsed = exchangeRateCreateSchema.parse(input)\n ensureCurrencyCommandScope(ctx, parsed)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n // Validate currencies exist\n await validateCurrenciesExist(em, parsed.fromCurrencyCode, parsed.toCurrencyCode, parsed.organizationId, parsed.tenantId)\n\n // Check for duplicate rate (same pair + date + source)\n const existing = await em.findOne(ExchangeRate, {\n fromCurrencyCode: parsed.fromCurrencyCode,\n toCurrencyCode: parsed.toCurrencyCode,\n date: parsed.date,\n source: parsed.source,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n deletedAt: null,\n })\n if (existing) {\n throw conflict('Exchange rate for this currency pair, date, and source already exists')\n }\n\n const now = new Date()\n const record = em.create(ExchangeRate, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n fromCurrencyCode: parsed.fromCurrencyCode,\n toCurrencyCode: parsed.toCurrencyCode,\n rate: parsed.rate,\n date: parsed.date,\n source: parsed.source,\n type: parsed.type ?? null,\n isActive: parsed.isActive !== false,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(record)\n try {\n await em.flush()\n } catch (err) {\n if (isUniqueViolation(err, 'exchange_rates_pair_datetime_source_unique')) {\n throw conflict('Exchange rate for this currency pair, date, and source already exists')\n }\n throw err\n }\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: exchangeRateCrudEvents,\n })\n\n return { exchangeRateId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadExchangeRateSnapshot(em, result.exchangeRateId, ctx)\n },\n buildLog: async ({ snapshots }) => {\n const after = snapshots.after as ExchangeRateSnapshot | undefined\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.rates.audit.create', 'Create exchange rate'),\n resourceKind: 'currencies.exchange_rate',\n resourceId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n snapshotAfter: after,\n payload: { undo: { after } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ExchangeRateUndoPayload>(logEntry)\n const after = payload?.after ?? null\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(ExchangeRate, { id: after.id })\n if (!record) return\n record.deletedAt = new Date()\n record.isActive = false\n await em.flush()\n },\n redo: makeCreateRedo<ExchangeRate, ExchangeRateSnapshot, ExchangeRateCreateInput, { exchangeRateId: string }>({\n entityClass: ExchangeRate,\n dateFields: ['createdAt', 'updatedAt', 'deletedAt', 'date'],\n buildResult: (entity) => ({ exchangeRateId: entity.id }),\n events: exchangeRateCrudEvents,\n }),\n}\n\nconst updateExchangeRateCommand: CommandHandler<ExchangeRateUpdateInput, { exchangeRateId: string }> = {\n id: 'currencies.exchange_rates.update',\n async prepare(input, ctx) {\n requireId(input.id, 'Exchange rate ID is required')\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadExchangeRateSnapshot(em, input.id, ctx)\n return { before }\n },\n async execute(input, ctx) {\n const parsed = exchangeRateUpdateSchema.parse(input)\n requireId(parsed.id, 'Exchange rate ID is required')\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(\n ExchangeRate,\n buildCurrencyCommandWhere<ExchangeRate>(ctx, { id: parsed.id }),\n )\n if (!record) {\n throw new CrudHttpError(404, { error: 'Exchange rate not found' })\n }\n ensureCurrencyCommandScope(ctx, record)\n\n // Validate currencies if changed\n const fromCode = parsed.fromCurrencyCode ?? record.fromCurrencyCode\n const toCode = parsed.toCurrencyCode ?? record.toCurrencyCode\n if (parsed.fromCurrencyCode || parsed.toCurrencyCode) {\n await validateCurrenciesExist(em, fromCode, toCode, record.organizationId, record.tenantId)\n }\n\n // Check for duplicate if changing pair, date, or source\n if (parsed.fromCurrencyCode || parsed.toCurrencyCode || parsed.date || parsed.source) {\n const date = parsed.date ?? record.date\n const source = parsed.source ?? record.source\n const existing = await em.findOne(ExchangeRate, {\n fromCurrencyCode: fromCode,\n toCurrencyCode: toCode,\n date,\n source,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n id: { $ne: record.id },\n deletedAt: null,\n })\n if (existing) {\n throw conflict('Exchange rate for this currency pair, date, and source already exists')\n }\n }\n\n const allChanges = buildChanges(record as unknown as Record<string, unknown>, parsed, [\n 'fromCurrencyCode',\n 'toCurrencyCode',\n 'rate',\n 'date',\n 'source',\n 'type',\n 'isActive',\n ])\n const changes = Object.fromEntries(\n Object.entries(allChanges).filter(([, c]) => c.to !== undefined),\n ) as Record<string, { from: unknown; to: unknown }>\n\n if (Object.keys(changes).length === 0) {\n return { exchangeRateId: record.id }\n }\n\n for (const [key, change] of Object.entries(changes)) {\n ;(record as any)[key] = change.to\n }\n record.updatedAt = new Date()\n \n // Validate final state after merging changes\n if (record.fromCurrencyCode === record.toCurrencyCode) {\n throw new CrudHttpError(400, { error: 'From and To currencies must be different' })\n }\n \n const rateValue = parseFloat(record.rate)\n if (isNaN(rateValue) || rateValue <= 0) {\n throw new CrudHttpError(400, { error: 'Rate must be greater than zero' })\n }\n \n await em.flush()\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: exchangeRateCrudEvents,\n })\n\n return { exchangeRateId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadExchangeRateSnapshot(em, result.exchangeRateId, ctx)\n },\n buildLog: async ({ snapshots, result }) => {\n const before = snapshots.before as ExchangeRateSnapshot | undefined\n const after = snapshots.after as ExchangeRateSnapshot | undefined\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.rates.audit.update', 'Update exchange rate'),\n resourceKind: 'currencies.exchange_rate',\n resourceId: after.id,\n tenantId: after.tenantId,\n organizationId: after.organizationId,\n snapshotBefore: before ?? undefined,\n snapshotAfter: after,\n payload: { undo: { before, after } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ExchangeRateUndoPayload>(logEntry)\n const before = payload?.before ?? null\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(ExchangeRate, { id: before.id })\n if (!record) return\n Object.assign(record, {\n fromCurrencyCode: before.fromCurrencyCode,\n toCurrencyCode: before.toCurrencyCode,\n rate: before.rate,\n date: new Date(before.date),\n source: before.source,\n type: before.type,\n isActive: before.isActive,\n updatedAt: new Date(),\n })\n await em.flush()\n },\n}\n\nconst deleteExchangeRateCommand: CommandHandler<ExchangeRateDeleteInput, { exchangeRateId: string }> = {\n id: 'currencies.exchange_rates.delete',\n async prepare(input, ctx) {\n requireId(input.id, 'Exchange rate ID is required')\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadExchangeRateSnapshot(em, input.id, ctx)\n return { before }\n },\n async execute(input, ctx) {\n const parsed = exchangeRateDeleteSchema.parse(input)\n requireId(parsed.id, 'Exchange rate ID is required')\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(\n ExchangeRate,\n buildCurrencyCommandWhere<ExchangeRate>(ctx, { id: parsed.id }),\n )\n if (!record) {\n throw new CrudHttpError(404, { error: 'Exchange rate not found' })\n }\n ensureCurrencyCommandScope(ctx, record)\n\n record.deletedAt = new Date()\n record.isActive = false\n await em.flush()\n\n const de = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: exchangeRateCrudEvents,\n })\n\n return { exchangeRateId: record.id }\n },\n buildLog: async ({ snapshots, result }) => {\n const before = snapshots.before as ExchangeRateSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('currencies.rates.audit.delete', 'Delete exchange rate'),\n resourceKind: 'currencies.exchange_rate',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: { undo: { before } },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ExchangeRateUndoPayload>(logEntry)\n const before = payload?.before ?? null\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(ExchangeRate, { id: before.id })\n if (!record) return\n record.deletedAt = null\n record.isActive = before.isActive\n record.updatedAt = new Date()\n await em.flush()\n },\n}\n\nregisterCommand(createExchangeRateCommand)\nregisterCommand(updateExchangeRateCommand)\nregisterCommand(deleteExchangeRateCommand)\n\nexport { createExchangeRateCommand, updateExchangeRateCommand, deleteExchangeRateCommand }\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,22 @@
1
+ import { buildScopedWhere } from "@open-mercato/shared/lib/api/crud";
2
+ import {
3
+ ensureOrganizationScope,
4
+ ensureTenantScope
5
+ } from "@open-mercato/shared/lib/commands/scope";
6
+ function buildCurrencyCommandWhere(ctx, base) {
7
+ const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? void 0;
8
+ return buildScopedWhere(base, {
9
+ organizationId,
10
+ organizationIds: ctx.organizationIds ?? void 0,
11
+ tenantId: ctx.auth?.tenantId ?? void 0
12
+ });
13
+ }
14
+ function ensureCurrencyCommandScope(ctx, record) {
15
+ ensureTenantScope(ctx, record.tenantId);
16
+ ensureOrganizationScope(ctx, record.organizationId);
17
+ }
18
+ export {
19
+ buildCurrencyCommandWhere,
20
+ ensureCurrencyCommandScope
21
+ };
22
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/currencies/commands/scope.ts"],
4
+ "sourcesContent": ["import type { FilterQuery } from '@mikro-orm/core'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { buildScopedWhere } from '@open-mercato/shared/lib/api/crud'\nimport {\n ensureOrganizationScope,\n ensureTenantScope,\n} from '@open-mercato/shared/lib/commands/scope'\n\ntype CurrencyCommandScopedRecord = {\n organizationId: string\n tenantId: string\n}\n\nexport function buildCurrencyCommandWhere<T extends object>(\n ctx: CommandRuntimeContext,\n base: FilterQuery<T>,\n): FilterQuery<T> {\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? undefined\n return buildScopedWhere(base as Record<string, unknown>, {\n organizationId,\n organizationIds: ctx.organizationIds ?? undefined,\n tenantId: ctx.auth?.tenantId ?? undefined,\n }) as FilterQuery<T>\n}\n\nexport function ensureCurrencyCommandScope(\n ctx: CommandRuntimeContext,\n record: CurrencyCommandScopedRecord,\n): void {\n ensureTenantScope(ctx, record.tenantId)\n ensureOrganizationScope(ctx, record.organizationId)\n}\n"],
5
+ "mappings": "AAEA,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAOA,SAAS,0BACd,KACA,MACgB;AAChB,QAAM,iBAAiB,IAAI,0BAA0B,IAAI,MAAM,SAAS;AACxE,SAAO,iBAAiB,MAAiC;AAAA,IACvD;AAAA,IACA,iBAAiB,IAAI,mBAAmB;AAAA,IACxC,UAAU,IAAI,MAAM,YAAY;AAAA,EAClC,CAAC;AACH;AAEO,SAAS,2BACd,KACA,QACM;AACN,oBAAkB,KAAK,OAAO,QAAQ;AACtC,0BAAwB,KAAK,OAAO,cAAc;AACpD;",
6
+ "names": []
7
+ }
@@ -75,7 +75,8 @@ function ActivitiesSection({
75
75
  onLoadingChange,
76
76
  refreshKey = 0,
77
77
  onEditActivity,
78
- runGuardedMutation
78
+ runGuardedMutation,
79
+ excludeInteractionType = "task"
79
80
  }) {
80
81
  const t = useT();
81
82
  const [filterTypes, setFilterTypes] = React.useState([]);
@@ -126,14 +127,14 @@ function ActivitiesSection({
126
127
  }
127
128
  setLoading(true);
128
129
  try {
129
- const taskFilterActive = filterTypes.includes("task");
130
+ const excludedFilterActive = excludeInteractionType ? filterTypes.includes(excludeInteractionType) : true;
130
131
  const canonicalParams = new URLSearchParams({
131
132
  entityId,
132
133
  limit: "50",
133
134
  sortField: "occurredAt",
134
135
  sortDir: "desc"
135
136
  });
136
- if (!taskFilterActive) canonicalParams.set("excludeInteractionType", "task");
137
+ if (excludeInteractionType && !excludedFilterActive) canonicalParams.set("excludeInteractionType", excludeInteractionType);
137
138
  if (dealId) canonicalParams.set("dealId", dealId);
138
139
  if (filterTypes.length > 0) canonicalParams.set("type", filterTypes.join(","));
139
140
  if (filterDateFrom) canonicalParams.set("from", filterDateFrom);
@@ -205,7 +206,7 @@ function ActivitiesSection({
205
206
  } finally {
206
207
  setLoading(false);
207
208
  }
208
- }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, loadedPages, useCanonicalInteractions, refreshKey, t]);
209
+ }, [dealId, entityId, excludeInteractionType, filterDateFrom, filterDateTo, filterTypes, loadedPages, useCanonicalInteractions, refreshKey, t]);
209
210
  React.useEffect(() => {
210
211
  setLoadedPages(1);
211
212
  }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, useCanonicalInteractions]);