@open-mercato/core 0.6.7-develop.6653.1.b5bc7194a0 → 0.6.7-develop.6661.1.0043ed1d03
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/business_rules/lib/value-resolver.js +7 -0
- package/dist/modules/business_rules/lib/value-resolver.js.map +2 -2
- package/dist/modules/communication_channels/lib/pg-errors.js +1 -7
- package/dist/modules/communication_channels/lib/pg-errors.js.map +2 -2
- package/dist/modules/currencies/commands/currencies.js +22 -8
- package/dist/modules/currencies/commands/currencies.js.map +2 -2
- package/dist/modules/currencies/commands/exchange-rates.js +22 -8
- package/dist/modules/currencies/commands/exchange-rates.js.map +2 -2
- package/dist/modules/currencies/commands/scope.js +22 -0
- package/dist/modules/currencies/commands/scope.js.map +7 -0
- package/dist/modules/customers/components/detail/ActivitiesSection.js +5 -4
- package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityHistorySection.js +5 -4
- package/dist/modules/customers/components/detail/ActivityHistorySection.js.map +2 -2
- package/dist/modules/feature_toggles/api/global/route.js +6 -0
- package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
- package/dist/modules/feature_toggles/commands/global.js +9 -9
- package/dist/modules/feature_toggles/commands/global.js.map +2 -2
- package/dist/modules/query_index/api/openapi.js +2 -2
- package/dist/modules/query_index/api/openapi.js.map +2 -2
- package/dist/modules/query_index/api/status.js +6 -4
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/query_index/cli.js +31 -28
- package/dist/modules/query_index/cli.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +4 -1
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/query_index/lib/batch.js +163 -44
- package/dist/modules/query_index/lib/batch.js.map +3 -3
- package/dist/modules/query_index/lib/jobs.js +3 -2
- package/dist/modules/query_index/lib/jobs.js.map +2 -2
- package/dist/modules/query_index/lib/reindexer.js +50 -16
- package/dist/modules/query_index/lib/reindexer.js.map +2 -2
- package/dist/modules/query_index/lib/stale.js +3 -0
- package/dist/modules/query_index/lib/stale.js.map +2 -2
- package/dist/modules/query_index/lib/subscriber-scope.js +63 -10
- package/dist/modules/query_index/lib/subscriber-scope.js.map +2 -2
- package/dist/modules/query_index/subscribers/delete_one.js +20 -6
- package/dist/modules/query_index/subscribers/delete_one.js.map +2 -2
- package/dist/modules/query_index/subscribers/upsert_one.js +8 -3
- package/dist/modules/query_index/subscribers/upsert_one.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/customize/route.js +2 -0
- package/dist/modules/workflows/api/definitions/[id]/customize/route.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js +4 -0
- package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/business_rules/lib/value-resolver.ts +16 -0
- package/src/modules/communication_channels/lib/pg-errors.ts +3 -10
- package/src/modules/currencies/commands/currencies.ts +26 -8
- package/src/modules/currencies/commands/exchange-rates.ts +26 -8
- package/src/modules/currencies/commands/scope.ts +32 -0
- package/src/modules/customers/components/detail/ActivitiesSection.tsx +10 -6
- package/src/modules/customers/components/detail/ActivityHistorySection.tsx +6 -3
- package/src/modules/feature_toggles/api/global/route.ts +7 -1
- package/src/modules/feature_toggles/commands/global.ts +9 -12
- package/src/modules/query_index/api/openapi.ts +2 -2
- package/src/modules/query_index/api/status.ts +14 -5
- package/src/modules/query_index/cli.ts +36 -28
- package/src/modules/query_index/components/QueryIndexesTable.tsx +7 -4
- package/src/modules/query_index/i18n/de.json +2 -0
- package/src/modules/query_index/i18n/en.json +2 -0
- package/src/modules/query_index/i18n/es.json +2 -0
- package/src/modules/query_index/i18n/pl.json +2 -0
- package/src/modules/query_index/lib/batch.ts +244 -67
- package/src/modules/query_index/lib/jobs.ts +11 -0
- package/src/modules/query_index/lib/reindexer.ts +69 -16
- package/src/modules/query_index/lib/stale.ts +10 -0
- package/src/modules/query_index/lib/subscriber-scope.ts +98 -14
- package/src/modules/query_index/subscribers/delete_one.ts +21 -10
- package/src/modules/query_index/subscribers/upsert_one.ts +8 -3
- package/src/modules/workflows/AGENTS.md +22 -0
- package/src/modules/workflows/api/definitions/[id]/customize/route.ts +9 -0
- package/src/modules/workflows/api/definitions/[id]/reset-to-code/route.ts +7 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -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;
|
|
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
|
}
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
if (!err || typeof err !== "object") return false;
|
|
3
|
-
const code = err.code;
|
|
4
|
-
if (code === "23505") return true;
|
|
5
|
-
const message = err.message;
|
|
6
|
-
return typeof message === "string" && /duplicate key value|unique constraint/i.test(message);
|
|
7
|
-
}
|
|
1
|
+
import { isUniqueViolation } from "@open-mercato/shared/lib/db/pg-errors";
|
|
8
2
|
export {
|
|
9
3
|
isUniqueViolation
|
|
10
4
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/communication_channels/lib/pg-errors.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n *
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * @deprecated Import `isUniqueViolation` from `@open-mercato/shared/lib/db/pg-errors` instead.\n * Kept as a re-export so existing hub call sites keep working.\n */\nexport { isUniqueViolation } from '@open-mercato/shared/lib/db/pg-errors'\n"],
|
|
5
|
+
"mappings": "AAIA,SAAS,yBAAyB;",
|
|
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(
|
|
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(
|
|
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(
|
|
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": "AAAA,SAAS,uBAAuB;AAEhC,SAAS,cAAc,WAAW,2BAA2B;AAC7D,SAAS,0BAA4C;AACrD,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAEhC,SAAS,eAAe,UAAU,yBAAyB;AAC3D,SAAS,2BAA2B;AACpC,SAAS,UAAU,oBAAoB;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;
|
|
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(
|
|
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(
|
|
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(
|
|
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": "AAAA,SAAS,uBAAuB;AAEhC,SAAS,cAAc,WAAW,2BAA2B;AAC7D,SAAS,0BAA4C;AACrD,SAAS,sBAAsB;AAE/B,SAAS,eAAe,UAAU,yBAAyB;AAC3D,SAAS,2BAA2B;AACpC,SAAS,cAAc,gBAAgB;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;
|
|
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
|
|
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 (!
|
|
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]);
|