@open-mercato/core 0.10.1-develop.7671.1.9117e4b0b3 → 0.10.1-develop.7672.1.c2e4c0b5df
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
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
2
2
|
import { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId } from "@open-mercato/shared/lib/commands/helpers";
|
|
3
|
-
import {
|
|
3
|
+
import { LockMode } from "@mikro-orm/core";
|
|
4
|
+
import { buildScopedWhere } from "@open-mercato/shared/lib/api/crud";
|
|
5
|
+
import { isUnrestrictedOrganizationScope } from "@open-mercato/shared/lib/auth/organizationAccess";
|
|
6
|
+
import { CustomerEntity, CustomerTag, CustomerTagAssignment } from "../data/entities.js";
|
|
4
7
|
import {
|
|
5
8
|
tagCreateSchema,
|
|
6
9
|
tagUpdateSchema,
|
|
@@ -12,11 +15,13 @@ import {
|
|
|
12
15
|
extractUndoPayload,
|
|
13
16
|
requireCustomerEntity,
|
|
14
17
|
ensureSameScope,
|
|
15
|
-
loadEntityTagIds
|
|
18
|
+
loadEntityTagIds,
|
|
19
|
+
emitQueryIndexUpsertEvents
|
|
16
20
|
} from "./shared.js";
|
|
17
21
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
18
|
-
import { CrudHttpError, notFound } from "@open-mercato/shared/lib/crud/errors";
|
|
19
|
-
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
22
|
+
import { CrudHttpError, forbidden, notFound } from "@open-mercato/shared/lib/crud/errors";
|
|
23
|
+
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
24
|
+
import { invalidateCrudCache } from "@open-mercato/shared/lib/crud/cache";
|
|
20
25
|
import { emitCustomersEvent } from "../events.js";
|
|
21
26
|
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
22
27
|
import { makeCreateRedo } from "@open-mercato/shared/lib/commands/redo";
|
|
@@ -34,8 +39,33 @@ const tagCrudEvents = {
|
|
|
34
39
|
tenantId: ctx.identifiers.tenantId
|
|
35
40
|
})
|
|
36
41
|
};
|
|
37
|
-
|
|
38
|
-
const
|
|
42
|
+
function tagDeleteLookup(id, ctx) {
|
|
43
|
+
const isSuperAdmin = ctx.auth?.isSuperAdmin === true;
|
|
44
|
+
const organizationScope = ctx.organizationScope;
|
|
45
|
+
const tenantId = ctx.auth ? ctx.auth.tenantId ?? null : organizationScope?.tenantId ?? null;
|
|
46
|
+
if (ctx.auth && !isSuperAdmin && (!tenantId || organizationScope && !organizationScope.tenantId)) {
|
|
47
|
+
throw forbidden();
|
|
48
|
+
}
|
|
49
|
+
let organizationIds;
|
|
50
|
+
if (organizationScope) {
|
|
51
|
+
if (!isUnrestrictedOrganizationScope({ isSuperAdmin, allowedOrganizationIds: organizationScope.allowedIds })) {
|
|
52
|
+
organizationIds = organizationScope.allowedIds ?? [];
|
|
53
|
+
}
|
|
54
|
+
} else if (!isSuperAdmin) {
|
|
55
|
+
const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId;
|
|
56
|
+
if (organizationId) organizationIds = [organizationId];
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
where: buildScopedWhere({ id }, {
|
|
60
|
+
tenantId: tenantId ?? void 0,
|
|
61
|
+
organizationIds,
|
|
62
|
+
softDeleteField: null
|
|
63
|
+
}),
|
|
64
|
+
scope: { tenantId, organizationId: organizationIds?.length === 1 ? organizationIds[0] : null }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async function loadTagSnapshot(em, id, lookup) {
|
|
68
|
+
const tag = lookup ? await findOneWithDecryption(em, CustomerTag, lookup.where, {}, lookup.scope) : await em.findOne(CustomerTag, { id });
|
|
39
69
|
if (!tag) return null;
|
|
40
70
|
return {
|
|
41
71
|
id: tag.id,
|
|
@@ -47,6 +77,39 @@ async function loadTagSnapshot(em, id) {
|
|
|
47
77
|
description: tag.description ?? null
|
|
48
78
|
};
|
|
49
79
|
}
|
|
80
|
+
async function loadTagAssignments(em, tag) {
|
|
81
|
+
const scope = { tenantId: tag.tenantId, organizationId: tag.organizationId };
|
|
82
|
+
return findWithDecryption(em, CustomerTagAssignment, { tag: tag.id, ...scope }, {}, scope);
|
|
83
|
+
}
|
|
84
|
+
async function refreshTagMemberships(ctx, tag, assignments, action) {
|
|
85
|
+
const dataEngine = ctx.container.resolve("dataEngine");
|
|
86
|
+
for (const assignment of assignments) {
|
|
87
|
+
await emitCrudSideEffects({
|
|
88
|
+
dataEngine,
|
|
89
|
+
action,
|
|
90
|
+
entity: assignment,
|
|
91
|
+
identifiers: { id: assignment.id, tenantId: tag.tenantId, organizationId: tag.organizationId },
|
|
92
|
+
indexer: { entityType: E.customers.customer_tag_assignment }
|
|
93
|
+
});
|
|
94
|
+
await emitCustomersEvent(action === "created" ? "customers.tag.assigned" : "customers.tag.removed", {
|
|
95
|
+
id: assignment.id,
|
|
96
|
+
tagId: tag.id,
|
|
97
|
+
entityId: assignment.entity.id,
|
|
98
|
+
organizationId: tag.organizationId,
|
|
99
|
+
tenantId: tag.tenantId
|
|
100
|
+
}, { persistent: true });
|
|
101
|
+
}
|
|
102
|
+
await emitQueryIndexUpsertEvents(ctx, assignments.map((assignment) => ({
|
|
103
|
+
entityType: E.customers.customer_entity,
|
|
104
|
+
recordId: assignment.entity.id,
|
|
105
|
+
tenantId: tag.tenantId,
|
|
106
|
+
organizationId: tag.organizationId
|
|
107
|
+
})));
|
|
108
|
+
await invalidateCrudCache(ctx.container, "customers.tagAssignment", {
|
|
109
|
+
tenantId: tag.tenantId,
|
|
110
|
+
organizationId: tag.organizationId
|
|
111
|
+
}, tag.tenantId, `customers.tags.delete.${action}`, ["customers.person", "customers.company"]);
|
|
112
|
+
}
|
|
50
113
|
const createTagCommand = {
|
|
51
114
|
id: "customers.tags.create",
|
|
52
115
|
async execute(rawInput, ctx) {
|
|
@@ -235,38 +298,63 @@ const updateTagCommand = {
|
|
|
235
298
|
};
|
|
236
299
|
const deleteTagCommand = {
|
|
237
300
|
id: "customers.tags.delete",
|
|
301
|
+
prepareSnapshotInsideTransaction: true,
|
|
238
302
|
async prepare(input, ctx) {
|
|
239
303
|
const id = requireId(input, "Tag id required");
|
|
240
|
-
const em = ctx.container.resolve("em");
|
|
241
|
-
const snapshot = await loadTagSnapshot(em, id);
|
|
242
|
-
|
|
304
|
+
const em = ctx.transactionalEm ?? ctx.container.resolve("em").fork();
|
|
305
|
+
const snapshot = await loadTagSnapshot(em, id, tagDeleteLookup(id, ctx));
|
|
306
|
+
if (!snapshot) return {};
|
|
307
|
+
ensureTenantScope(ctx, snapshot.tenantId);
|
|
308
|
+
ensureOrganizationScope(ctx, snapshot.organizationId);
|
|
309
|
+
const assignments = await findWithDecryption(em, CustomerTagAssignment, {
|
|
310
|
+
tag: id,
|
|
311
|
+
tenantId: snapshot.tenantId,
|
|
312
|
+
organizationId: snapshot.organizationId
|
|
313
|
+
}, {}, snapshot);
|
|
314
|
+
snapshot.assignments = assignments.map((assignment) => ({
|
|
315
|
+
id: assignment.id,
|
|
316
|
+
entityId: assignment.entity.id,
|
|
317
|
+
tenantId: assignment.tenantId,
|
|
318
|
+
organizationId: assignment.organizationId,
|
|
319
|
+
createdAt: assignment.createdAt.toISOString()
|
|
320
|
+
}));
|
|
321
|
+
return { before: snapshot };
|
|
243
322
|
},
|
|
244
323
|
async execute(input, ctx) {
|
|
245
324
|
const id = requireId(input, "Tag id required");
|
|
325
|
+
const lookup = tagDeleteLookup(id, ctx);
|
|
246
326
|
const em = ctx.container.resolve("em").fork();
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
ensureTenantScope(ctx, tag.tenantId);
|
|
250
|
-
ensureOrganizationScope(ctx, tag.organizationId);
|
|
327
|
+
let tag = null;
|
|
328
|
+
let assignments = [];
|
|
251
329
|
await withAtomicFlush(em, [
|
|
252
330
|
async () => {
|
|
253
|
-
await em.
|
|
331
|
+
tag = await findOneWithDecryption(em, CustomerTag, lookup.where, { lockMode: LockMode.PESSIMISTIC_WRITE }, lookup.scope);
|
|
332
|
+
if (!tag) throw notFound("Tag not found");
|
|
333
|
+
ensureTenantScope(ctx, tag.tenantId);
|
|
334
|
+
ensureOrganizationScope(ctx, tag.organizationId);
|
|
335
|
+
await ctx.beforeTransactionalWrite?.(em, input);
|
|
336
|
+
assignments = await loadTagAssignments(em, tag);
|
|
337
|
+
await em.nativeDelete(CustomerTagAssignment, { tag, tenantId: tag.tenantId, organizationId: tag.organizationId });
|
|
254
338
|
em.remove(tag);
|
|
255
339
|
}
|
|
256
|
-
], { transaction: true });
|
|
340
|
+
], { transaction: true, label: "customers.tags.delete" });
|
|
341
|
+
if (!tag) throw notFound("Tag not found");
|
|
342
|
+
const deletedTag = tag;
|
|
257
343
|
const de = ctx.container.resolve("dataEngine");
|
|
258
344
|
await emitCrudSideEffects({
|
|
259
345
|
dataEngine: de,
|
|
260
346
|
action: "deleted",
|
|
261
|
-
entity:
|
|
347
|
+
entity: deletedTag,
|
|
262
348
|
identifiers: {
|
|
263
|
-
id:
|
|
264
|
-
organizationId:
|
|
265
|
-
tenantId:
|
|
349
|
+
id: deletedTag.id,
|
|
350
|
+
organizationId: deletedTag.organizationId,
|
|
351
|
+
tenantId: deletedTag.tenantId
|
|
266
352
|
},
|
|
267
|
-
events: tagCrudEvents
|
|
353
|
+
events: tagCrudEvents,
|
|
354
|
+
indexer: tagCrudIndexer
|
|
268
355
|
});
|
|
269
|
-
|
|
356
|
+
await refreshTagMemberships(ctx, deletedTag, assignments, "deleted");
|
|
357
|
+
return { tagId: deletedTag.id };
|
|
270
358
|
},
|
|
271
359
|
buildLog: async ({ snapshots }) => {
|
|
272
360
|
const before = snapshots.before;
|
|
@@ -278,6 +366,7 @@ const deleteTagCommand = {
|
|
|
278
366
|
resourceId: before.id,
|
|
279
367
|
tenantId: before.tenantId,
|
|
280
368
|
organizationId: before.organizationId,
|
|
369
|
+
context: { cacheAliases: ["customers.tagAssignment", "customers.person", "customers.company"] },
|
|
281
370
|
snapshotBefore: before,
|
|
282
371
|
payload: {
|
|
283
372
|
undo: {
|
|
@@ -290,44 +379,65 @@ const deleteTagCommand = {
|
|
|
290
379
|
const payload = extractUndoPayload(logEntry);
|
|
291
380
|
const before = payload?.before;
|
|
292
381
|
if (!before) return;
|
|
382
|
+
ensureTenantScope(ctx, before.tenantId);
|
|
383
|
+
ensureOrganizationScope(ctx, before.organizationId);
|
|
293
384
|
const em = ctx.container.resolve("em").fork();
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
organizationId: before.organizationId,
|
|
299
|
-
tenantId: before.tenantId,
|
|
300
|
-
slug: before.slug,
|
|
301
|
-
label: before.label,
|
|
302
|
-
color: before.color,
|
|
303
|
-
description: before.description
|
|
304
|
-
});
|
|
305
|
-
em.persist(tag);
|
|
306
|
-
} else {
|
|
307
|
-
tag.slug = before.slug;
|
|
308
|
-
tag.label = before.label;
|
|
309
|
-
tag.color = before.color;
|
|
310
|
-
tag.description = before.description;
|
|
311
|
-
}
|
|
312
|
-
const restoredTag = tag;
|
|
385
|
+
const scope = { tenantId: before.tenantId, organizationId: before.organizationId };
|
|
386
|
+
const existingTag = await findOneWithDecryption(em, CustomerTag, { id: before.id, ...scope }, {}, scope);
|
|
387
|
+
const tag = existingTag ?? em.create(CustomerTag, { id: before.id, ...scope, slug: before.slug, label: before.label });
|
|
388
|
+
const restoredAssignments = [];
|
|
313
389
|
await withAtomicFlush(em, [
|
|
314
390
|
() => {
|
|
315
|
-
|
|
391
|
+
tag.slug = before.slug;
|
|
392
|
+
tag.label = before.label;
|
|
393
|
+
tag.color = before.color;
|
|
394
|
+
tag.description = before.description;
|
|
395
|
+
em.persist(tag);
|
|
396
|
+
},
|
|
397
|
+
async () => {
|
|
398
|
+
const assignments = (before.assignments ?? []).filter(
|
|
399
|
+
(assignment) => assignment.tenantId === before.tenantId && assignment.organizationId === before.organizationId
|
|
400
|
+
);
|
|
401
|
+
if (!assignments.length) return;
|
|
402
|
+
const customers = await findWithDecryption(em, CustomerEntity, {
|
|
403
|
+
id: { $in: assignments.map((assignment) => assignment.entityId) },
|
|
404
|
+
...scope,
|
|
405
|
+
deletedAt: null,
|
|
406
|
+
kind: { $in: ["person", "company"] }
|
|
407
|
+
}, {}, scope);
|
|
408
|
+
const existingAssignments = await loadTagAssignments(em, tag);
|
|
409
|
+
const assignedEntityIds = new Set(existingAssignments.map((assignment) => assignment.entity.id));
|
|
410
|
+
const customersById = new Map(customers.map((customer) => [customer.id, customer]));
|
|
411
|
+
for (const snapshot of assignments) {
|
|
412
|
+
const entity = customersById.get(snapshot.entityId);
|
|
413
|
+
if (!entity || assignedEntityIds.has(entity.id)) continue;
|
|
414
|
+
const assignment = em.create(CustomerTagAssignment, {
|
|
415
|
+
id: snapshot.id,
|
|
416
|
+
tag,
|
|
417
|
+
entity,
|
|
418
|
+
...scope,
|
|
419
|
+
createdAt: new Date(snapshot.createdAt)
|
|
420
|
+
});
|
|
421
|
+
em.persist(assignment);
|
|
422
|
+
restoredAssignments.push(assignment);
|
|
423
|
+
assignedEntityIds.add(entity.id);
|
|
424
|
+
}
|
|
316
425
|
}
|
|
317
|
-
], { transaction: true });
|
|
426
|
+
], { transaction: true, label: "customers.tags.delete.undo" });
|
|
318
427
|
const de = ctx.container.resolve("dataEngine");
|
|
319
428
|
await emitCrudUndoSideEffects({
|
|
320
429
|
dataEngine: de,
|
|
321
430
|
action: "created",
|
|
322
431
|
entity: tag,
|
|
323
432
|
identifiers: {
|
|
324
|
-
id:
|
|
325
|
-
organizationId:
|
|
326
|
-
tenantId:
|
|
433
|
+
id: before.id,
|
|
434
|
+
organizationId: before.organizationId,
|
|
435
|
+
tenantId: before.tenantId
|
|
327
436
|
},
|
|
328
437
|
events: tagCrudEvents,
|
|
329
438
|
indexer: tagCrudIndexer
|
|
330
439
|
});
|
|
440
|
+
await refreshTagMemberships(ctx, tag, restoredAssignments, "created");
|
|
331
441
|
}
|
|
332
442
|
};
|
|
333
443
|
const assignTagCommand = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/commands/tags.ts"],
|
|
4
|
-
"sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId } from '@open-mercato/shared/lib/commands/helpers'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerTag, CustomerTagAssignment } from '../data/entities'\nimport {\n tagCreateSchema,\n tagUpdateSchema,\n tagAssignmentSchema,\n type TagCreateInput,\n type TagUpdateInput,\n type TagAssignmentInput,\n} from '../data/validators'\nimport {\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n requireCustomerEntity,\n ensureSameScope,\n loadEntityTagIds,\n} from './shared'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { emitCustomersEvent } from '../events'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'\nimport { E } from '#generated/entities.ids.generated'\n\n// The route (`api/tags/route.ts`) declares this same entity type and hands it down to the\n// execute path, but an undo runs through `CommandBus.undo()` where no route declaration is\n// active \u2014 so the undo marks carry the indexer explicitly, or a restored tag would stay\n// invisible to every index-backed list until the next full rebuild (#5741).\nconst tagCrudIndexer: CrudIndexerConfig<CustomerTag> = {\n entityType: E.customers.customer_tag,\n}\n\nconst tagCrudEvents: CrudEventsConfig = {\n module: 'customers',\n entity: 'tag',\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 TagSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n slug: string\n label: string\n color: string | null\n description: string | null\n}\n\ntype TagUndoPayload = {\n before?: TagSnapshot | null\n after?: TagSnapshot | null\n}\n\ntype TagAssignmentSnapshot = {\n tagId: string\n entityId: string\n organizationId: string\n tenantId: string\n}\n\ntype TagAssignmentUndoPayload = {\n before?: TagAssignmentSnapshot | null\n}\n\nasync function loadTagSnapshot(em: EntityManager, id: string): Promise<TagSnapshot | null> {\n const tag = await em.findOne(CustomerTag, { id })\n if (!tag) return null\n return {\n id: tag.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n slug: tag.slug,\n label: tag.label,\n color: tag.color ?? null,\n description: tag.description ?? null,\n }\n}\n\nconst createTagCommand: CommandHandler<TagCreateInput, { tagId: string }> = {\n id: 'customers.tags.create',\n async execute(rawInput, ctx) {\n const parsed = tagCreateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const duplicate = await em.findOne(CustomerTag, {\n slug: parsed.slug,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n })\n if (duplicate) throw new CrudHttpError(409, { error: 'Tag slug already exists' })\n\n const tag = em.create(CustomerTag, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n slug: parsed.slug,\n label: parsed.label,\n color: parsed.color ?? null,\n description: parsed.description ?? null,\n })\n em.persist(tag)\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: tag,\n identifiers: {\n id: tag.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n },\n events: tagCrudEvents,\n })\n\n return { tagId: tag.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadTagSnapshot(em, result.tagId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadTagSnapshot(em, result.tagId)\n return {\n actionLabel: translate('customers.audit.tags.create', 'Create tag'),\n resourceKind: 'customers.tag',\n resourceId: result.tagId,\n tenantId: snapshot?.tenantId ?? null,\n organizationId: snapshot?.organizationId ?? null,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies TagUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const tagId = logEntry?.resourceId ?? null\n if (!tagId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id: tagId })\n if (tag) {\n await em.nativeDelete(CustomerTagAssignment, { tag })\n em.remove(tag)\n await em.flush()\n }\n },\n redo: makeCreateRedo<CustomerTag, TagSnapshot, TagCreateInput, { tagId: string }>({\n entityClass: CustomerTag,\n buildResult: (entity) => ({ tagId: entity.id }),\n events: tagCrudEvents,\n }),\n}\n\nconst updateTagCommand: CommandHandler<TagUpdateInput, { tagId: string }> = {\n id: 'customers.tags.update',\n async prepare(rawInput, ctx) {\n const parsed = tagUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadTagSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const parsed = tagUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id: parsed.id })\n if (!tag) throw notFound('Tag not found')\n ensureTenantScope(ctx, tag.tenantId)\n ensureOrganizationScope(ctx, tag.organizationId)\n\n if (parsed.slug !== undefined && parsed.slug !== tag.slug) {\n const duplicate = await em.findOne(CustomerTag, {\n slug: parsed.slug,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n id: { $ne: tag.id },\n })\n if (duplicate) throw new CrudHttpError(409, { error: 'Tag slug already exists' })\n tag.slug = parsed.slug\n }\n if (parsed.label !== undefined) tag.label = parsed.label\n if (parsed.color !== undefined) tag.color = parsed.color ?? null\n if (parsed.description !== undefined) tag.description = parsed.description ?? null\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: tag,\n identifiers: {\n id: tag.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n },\n events: tagCrudEvents,\n })\n\n return { tagId: tag.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as TagSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadTagSnapshot(em, before.id)\n const changes =\n afterSnapshot && before\n ? buildChanges(\n before as unknown as Record<string, unknown>,\n afterSnapshot as unknown as Record<string, unknown>,\n ['slug', 'label', 'color', 'description']\n )\n : {}\n return {\n actionLabel: translate('customers.audit.tags.update', 'Update tag'),\n resourceKind: 'customers.tag',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies TagUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TagUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let tag = await em.findOne(CustomerTag, { id: before.id })\n if (!tag) {\n tag = em.create(CustomerTag, {\n id: before.id,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n slug: before.slug,\n label: before.label,\n color: before.color,\n description: before.description,\n })\n em.persist(tag)\n } else {\n tag.slug = before.slug\n tag.label = before.label\n tag.color = before.color\n tag.description = before.description\n }\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: tag,\n identifiers: {\n id: tag.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n },\n events: tagCrudEvents,\n indexer: tagCrudIndexer,\n })\n },\n}\n\nconst deleteTagCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { tagId: string }> = {\n id: 'customers.tags.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Tag id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadTagSnapshot(em, id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Tag id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id })\n if (!tag) throw notFound('Tag not found')\n ensureTenantScope(ctx, tag.tenantId)\n ensureOrganizationScope(ctx, tag.organizationId)\n await withAtomicFlush(em, [\n async () => {\n await em.nativeDelete(CustomerTagAssignment, { tag })\n em.remove(tag)\n },\n ], { transaction: true })\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: tag,\n identifiers: {\n id: tag.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n },\n events: tagCrudEvents,\n })\n return { tagId: tag.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as TagSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('customers.audit.tags.delete', 'Delete tag'),\n resourceKind: 'customers.tag',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n } satisfies TagUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TagUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let tag = await em.findOne(CustomerTag, { id: before.id })\n if (!tag) {\n tag = em.create(CustomerTag, {\n id: before.id,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n slug: before.slug,\n label: before.label,\n color: before.color,\n description: before.description,\n })\n em.persist(tag)\n } else {\n tag.slug = before.slug\n tag.label = before.label\n tag.color = before.color\n tag.description = before.description\n }\n const restoredTag = tag\n await withAtomicFlush(em, [\n () => {\n em.persist(restoredTag)\n },\n ], { transaction: true })\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'created',\n entity: tag,\n identifiers: {\n id: tag.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n },\n events: tagCrudEvents,\n indexer: tagCrudIndexer,\n })\n },\n}\n\nconst assignTagCommand: CommandHandler<TagAssignmentInput, { assignmentId: string }> = {\n id: 'customers.tags.assign',\n async execute(rawInput, ctx) {\n const parsed = tagAssignmentSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id: parsed.tagId, tenantId: parsed.tenantId, organizationId: parsed.organizationId })\n if (!tag) throw notFound('Tag not found')\n const entity = await requireCustomerEntity(em, parsed.entityId, { tenantId: parsed.tenantId, organizationId: parsed.organizationId }, undefined, 'Customer not found')\n ensureSameScope(entity, parsed.organizationId, parsed.tenantId)\n const tagIds = await loadEntityTagIds(em, entity)\n if (tagIds.includes(parsed.tagId)) {\n throw new CrudHttpError(409, { error: 'Tag already assigned' })\n }\n\n const assignment = em.create(CustomerTagAssignment, {\n tag,\n entity,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n })\n em.persist(assignment)\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: assignment,\n identifiers: {\n id: String(assignment.id),\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n },\n })\n\n await emitCustomersEvent('customers.tag.assigned', {\n id: String(assignment.id),\n tagId: parsed.tagId,\n entityId: parsed.entityId,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n }, { persistent: true })\n\n return { assignmentId: assignment.id }\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const assignment = await findOneWithDecryption(\n em,\n CustomerTagAssignment,\n { id: result.assignmentId },\n { populate: ['tag', 'entity'] },\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null },\n )\n if (!assignment) return null\n const tagId = typeof assignment.tag === 'string' ? assignment.tag : assignment.tag.id\n const entityId = typeof assignment.entity === 'string' ? assignment.entity : assignment.entity.id\n return {\n actionLabel: translate('customers.audit.tags.assign', 'Assign tag'),\n resourceKind: 'customers.tagAssignment',\n resourceId: result.assignmentId,\n tenantId: assignment.tenantId,\n organizationId: assignment.organizationId,\n payload: {\n undo: {\n before: {\n tagId,\n entityId,\n tenantId: assignment.tenantId,\n organizationId: assignment.organizationId,\n } satisfies TagAssignmentSnapshot,\n } satisfies TagAssignmentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TagAssignmentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await em.nativeDelete(CustomerTagAssignment, {\n tag: before.tagId,\n entity: before.entityId,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n })\n },\n redo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TagAssignmentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) {\n throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for tag assign' })\n }\n const assignmentId = logEntry?.resourceId ?? null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id: before.tagId })\n if (!tag) throw notFound('Tag not found')\n const entity = await requireCustomerEntity(em, before.entityId, { tenantId: before.tenantId, organizationId: before.organizationId }, undefined, 'Customer not found')\n ensureSameScope(entity, before.organizationId, before.tenantId)\n\n let assignment = await em.findOne(CustomerTagAssignment, {\n tag,\n entity,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n })\n if (!assignment) {\n assignment = em.create(CustomerTagAssignment, {\n ...(assignmentId ? { id: assignmentId } : {}),\n tag,\n entity,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n })\n em.persist(assignment)\n await em.flush()\n }\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: assignment,\n identifiers: {\n id: String(assignment.id),\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n },\n })\n\n await emitCustomersEvent('customers.tag.assigned', {\n id: String(assignment.id),\n tagId: before.tagId,\n entityId: before.entityId,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n }, { persistent: true })\n\n return { assignmentId: assignment.id }\n },\n}\n\nconst unassignTagCommand: CommandHandler<TagAssignmentInput, { assignmentId: string | null }> = {\n id: 'customers.tags.unassign',\n async execute(rawInput, ctx) {\n const parsed = tagAssignmentSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await em.findOne(CustomerTagAssignment, {\n tag: parsed.tagId,\n entity: parsed.entityId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n if (!existing) throw notFound('Tag assignment not found')\n await em.remove(existing).flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: existing,\n identifiers: {\n id: String(existing.id),\n organizationId: existing.organizationId,\n tenantId: existing.tenantId,\n },\n })\n\n await emitCustomersEvent('customers.tag.removed', {\n id: String(existing.id),\n tagId: parsed.tagId,\n entityId: parsed.entityId,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n }, { persistent: true })\n\n return { assignmentId: existing.id ?? null }\n },\n buildLog: async ({ snapshots, input }) => {\n const { translate } = await resolveTranslations()\n const parsed = tagAssignmentSchema.parse(input)\n return {\n actionLabel: translate('customers.audit.tags.unassign', 'Unassign tag'),\n resourceKind: 'customers.tagAssignment',\n resourceId: parsed.tagId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n before: {\n tagId: parsed.tagId,\n entityId: parsed.entityId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n } satisfies TagAssignmentSnapshot,\n } satisfies TagAssignmentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TagAssignmentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id: before.tagId })\n const entity = await requireCustomerEntity(em, before.entityId, { tenantId: before.tenantId, organizationId: before.organizationId }, undefined, 'Customer not found')\n ensureSameScope(entity, before.organizationId, before.tenantId)\n if (!tag) throw notFound('Tag not found')\n const existing = await em.findOne(CustomerTagAssignment, {\n tag,\n entity,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n })\n if (!existing) {\n const assignment = em.create(CustomerTagAssignment, {\n tag,\n entity,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n })\n em.persist(assignment)\n await em.flush()\n }\n },\n}\n\nregisterCommand(createTagCommand)\nregisterCommand(updateTagCommand)\nregisterCommand(deleteTagCommand)\nregisterCommand(assignTagCommand)\nregisterCommand(unassignTagCommand)\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB,yBAAyB,cAAc,iBAAiB;AAGtF,SAAS,aAAa,6BAA6B;AACnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AACpC,SAAS,eAAe,gBAAgB;AAExC,SAAS,6BAA6B;AACtC,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AAC/B,SAAS,SAAS;AAMlB,MAAM,iBAAiD;AAAA,EACrD,YAAY,EAAE,UAAU;AAC1B;AAEA,MAAM,gBAAkC;AAAA,EACtC,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,cAAc,CAAC,SAAS;AAAA,IACtB,IAAI,IAAI,YAAY;AAAA,IACpB,gBAAgB,IAAI,YAAY;AAAA,IAChC,UAAU,IAAI,YAAY;AAAA,EAC5B;AACF;AA4BA,eAAe,gBAAgB,IAAmB,IAAyC;AACzF,QAAM,MAAM,MAAM,GAAG,QAAQ,aAAa,EAAE,GAAG,CAAC;AAChD,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,gBAAgB,IAAI;AAAA,IACpB,UAAU,IAAI;AAAA,IACd,MAAM,IAAI;AAAA,IACV,OAAO,IAAI;AAAA,IACX,OAAO,IAAI,SAAS;AAAA,IACpB,aAAa,IAAI,eAAe;AAAA,EAClC;AACF;AAEA,MAAM,mBAAsE;AAAA,EAC1E,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,SAAS,gBAAgB,MAAM,QAAQ;AAC7C,sBAAkB,KAAK,OAAO,QAAQ;AACtC,4BAAwB,KAAK,OAAO,cAAc;AAElD,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,YAAY,MAAM,GAAG,QAAQ,aAAa;AAAA,MAC9C,MAAM,OAAO;AAAA,MACb,gBAAgB,OAAO;AAAA,MACvB,UAAU,OAAO;AAAA,IACnB,CAAC;AACD,QAAI,UAAW,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,0BAA0B,CAAC;AAEhF,UAAM,MAAM,GAAG,OAAO,aAAa;AAAA,MACjC,gBAAgB,OAAO;AAAA,MACvB,UAAU,OAAO;AAAA,MACjB,MAAM,OAAO;AAAA,MACb,OAAO,OAAO;AAAA,MACd,OAAO,OAAO,SAAS;AAAA,MACvB,aAAa,OAAO,eAAe;AAAA,IACrC,CAAC;AACD,OAAG,QAAQ,GAAG;AACd,UAAM,GAAG,MAAM;AAEf,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,IAAI;AAAA,QACR,gBAAgB,IAAI;AAAA,QACpB,UAAU,IAAI;AAAA,MAChB;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AAED,WAAO,EAAE,OAAO,IAAI,GAAG;AAAA,EACzB;AAAA,EACA,cAAc,OAAO,QAAQ,QAAQ,QAAQ;AAC3C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,WAAO,MAAM,gBAAgB,IAAI,OAAO,KAAK;AAAA,EAC/C;AAAA,EACA,UAAU,OAAO,EAAE,QAAQ,IAAI,MAAM;AACnC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,WAAW,MAAM,gBAAgB,IAAI,OAAO,KAAK;AACvD,WAAO;AAAA,MACL,aAAa,UAAU,+BAA+B,YAAY;AAAA,MAClE,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,UAAU,UAAU,YAAY;AAAA,MAChC,gBAAgB,UAAU,kBAAkB;AAAA,MAC5C,eAAe,YAAY;AAAA,MAC3B,SAAS;AAAA,QACP,MAAM;AAAA,UACJ,OAAO,YAAY;AAAA,QACrB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,QAAQ,UAAU,cAAc;AACtC,QAAI,CAAC,MAAO;AACZ,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,MAAM,MAAM,GAAG,QAAQ,aAAa,EAAE,IAAI,MAAM,CAAC;AACvD,QAAI,KAAK;AACP,YAAM,GAAG,aAAa,uBAAuB,EAAE,IAAI,CAAC;AACpD,SAAG,OAAO,GAAG;AACb,YAAM,GAAG,MAAM;AAAA,IACjB;AAAA,EACF;AAAA,EACA,MAAM,eAA4E;AAAA,IAChF,aAAa;AAAA,IACb,aAAa,CAAC,YAAY,EAAE,OAAO,OAAO,GAAG;AAAA,IAC7C,QAAQ;AAAA,EACV,CAAC;AACH;AAEA,MAAM,mBAAsE;AAAA,EAC1E,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,SAAS,gBAAgB,MAAM,QAAQ;AAC7C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,UAAM,WAAW,MAAM,gBAAgB,IAAI,OAAO,EAAE;AACpD,WAAO,WAAW,EAAE,QAAQ,SAAS,IAAI,CAAC;AAAA,EAC5C;AAAA,EACA,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,SAAS,gBAAgB,MAAM,QAAQ;AAC7C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,MAAM,MAAM,GAAG,QAAQ,aAAa,EAAE,IAAI,OAAO,GAAG,CAAC;AAC3D,QAAI,CAAC,IAAK,OAAM,SAAS,eAAe;AACxC,sBAAkB,KAAK,IAAI,QAAQ;AACnC,4BAAwB,KAAK,IAAI,cAAc;AAE/C,QAAI,OAAO,SAAS,UAAa,OAAO,SAAS,IAAI,MAAM;AACzD,YAAM,YAAY,MAAM,GAAG,QAAQ,aAAa;AAAA,QAC9C,MAAM,OAAO;AAAA,QACb,gBAAgB,IAAI;AAAA,QACpB,UAAU,IAAI;AAAA,QACd,IAAI,EAAE,KAAK,IAAI,GAAG;AAAA,MACpB,CAAC;AACD,UAAI,UAAW,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,0BAA0B,CAAC;AAChF,UAAI,OAAO,OAAO;AAAA,IACpB;AACA,QAAI,OAAO,UAAU,OAAW,KAAI,QAAQ,OAAO;AACnD,QAAI,OAAO,UAAU,OAAW,KAAI,QAAQ,OAAO,SAAS;AAC5D,QAAI,OAAO,gBAAgB,OAAW,KAAI,cAAc,OAAO,eAAe;AAE9E,UAAM,GAAG,MAAM;AAEf,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,IAAI;AAAA,QACR,gBAAgB,IAAI;AAAA,QACpB,UAAU,IAAI;AAAA,MAChB;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AAED,WAAO,EAAE,OAAO,IAAI,GAAG;AAAA,EACzB;AAAA,EACA,UAAU,OAAO,EAAE,WAAW,IAAI,MAAM;AACtC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,gBAAgB,MAAM,gBAAgB,IAAI,OAAO,EAAE;AACzD,UAAM,UACJ,iBAAiB,SACb;AAAA,MACE;AAAA,MACA;AAAA,MACA,CAAC,QAAQ,SAAS,SAAS,aAAa;AAAA,IAC1C,IACA,CAAC;AACP,WAAO;AAAA,MACL,aAAa,UAAU,+BAA+B,YAAY;AAAA,MAClE,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,gBAAgB;AAAA,MAChB,eAAe,iBAAiB;AAAA,MAChC;AAAA,MACA,SAAS;AAAA,QACP,MAAM;AAAA,UACJ;AAAA,UACA,OAAO,iBAAiB;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,UAAU,mBAAmC,QAAQ;AAC3D,UAAM,SAAS,SAAS;AACxB,QAAI,CAAC,OAAQ;AACb,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,QAAI,MAAM,MAAM,GAAG,QAAQ,aAAa,EAAE,IAAI,OAAO,GAAG,CAAC;AACzD,QAAI,CAAC,KAAK;AACR,YAAM,GAAG,OAAO,aAAa;AAAA,QAC3B,IAAI,OAAO;AAAA,QACX,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,QACjB,MAAM,OAAO;AAAA,QACb,OAAO,OAAO;AAAA,QACd,OAAO,OAAO;AAAA,QACd,aAAa,OAAO;AAAA,MACtB,CAAC;AACD,SAAG,QAAQ,GAAG;AAAA,IAChB,OAAO;AACL,UAAI,OAAO,OAAO;AAClB,UAAI,QAAQ,OAAO;AACnB,UAAI,QAAQ,OAAO;AACnB,UAAI,cAAc,OAAO;AAAA,IAC3B;AACA,UAAM,GAAG,MAAM;AAEf,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,wBAAwB;AAAA,MAC5B,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,IAAI;AAAA,QACR,gBAAgB,IAAI;AAAA,QACpB,UAAU,IAAI;AAAA,MAChB;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAEA,MAAM,mBAA2H;AAAA,EAC/H,IAAI;AAAA,EACJ,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAM,KAAK,UAAU,OAAO,iBAAiB;AAC7C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI;AACtC,UAAM,WAAW,MAAM,gBAAgB,IAAI,EAAE;AAC7C,WAAO,WAAW,EAAE,QAAQ,SAAS,IAAI,CAAC;AAAA,EAC5C;AAAA,EACA,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAM,KAAK,UAAU,OAAO,iBAAiB;AAC7C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,MAAM,MAAM,GAAG,QAAQ,aAAa,EAAE,GAAG,CAAC;AAChD,QAAI,CAAC,IAAK,OAAM,SAAS,eAAe;AACxC,sBAAkB,KAAK,IAAI,QAAQ;AACnC,4BAAwB,KAAK,IAAI,cAAc;AAC/C,UAAM,gBAAgB,IAAI;AAAA,MACxB,YAAY;AACV,cAAM,GAAG,aAAa,uBAAuB,EAAE,IAAI,CAAC;AACpD,WAAG,OAAO,GAAG;AAAA,MACf;AAAA,IACF,GAAG,EAAE,aAAa,KAAK,CAAC;AAExB,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,IAAI;AAAA,QACR,gBAAgB,IAAI;AAAA,QACpB,UAAU,IAAI;AAAA,MAChB;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AACD,WAAO,EAAE,OAAO,IAAI,GAAG;AAAA,EACzB;AAAA,EACA,UAAU,OAAO,EAAE,UAAU,MAAM;AACjC,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO;AAAA,MACL,aAAa,UAAU,+BAA+B,YAAY;AAAA,MAClE,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,gBAAgB;AAAA,MAChB,SAAS;AAAA,QACP,MAAM;AAAA,UACJ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,UAAU,mBAAmC,QAAQ;AAC3D,UAAM,SAAS,SAAS;AACxB,QAAI,CAAC,OAAQ;AACb,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,QAAI,MAAM,MAAM,GAAG,QAAQ,aAAa,EAAE,IAAI,OAAO,GAAG,CAAC;AACzD,QAAI,CAAC,KAAK;AACR,YAAM,GAAG,OAAO,aAAa;AAAA,QAC3B,IAAI,OAAO;AAAA,QACX,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,QACjB,MAAM,OAAO;AAAA,QACb,OAAO,OAAO;AAAA,QACd,OAAO,OAAO;AAAA,QACd,aAAa,OAAO;AAAA,MACtB,CAAC;AACD,SAAG,QAAQ,GAAG;AAAA,IAChB,OAAO;AACL,UAAI,OAAO,OAAO;AAClB,UAAI,QAAQ,OAAO;AACnB,UAAI,QAAQ,OAAO;AACnB,UAAI,cAAc,OAAO;AAAA,IAC3B;AACA,UAAM,cAAc;AACpB,UAAM,gBAAgB,IAAI;AAAA,MACxB,MAAM;AACJ,WAAG,QAAQ,WAAW;AAAA,MACxB;AAAA,IACF,GAAG,EAAE,aAAa,KAAK,CAAC;AAExB,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,wBAAwB;AAAA,MAC5B,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,IAAI;AAAA,QACR,gBAAgB,IAAI;AAAA,QACpB,UAAU,IAAI;AAAA,MAChB;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAEA,MAAM,mBAAiF;AAAA,EACrF,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,SAAS,oBAAoB,MAAM,QAAQ;AACjD,sBAAkB,KAAK,OAAO,QAAQ;AACtC,4BAAwB,KAAK,OAAO,cAAc;AAElD,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AACjE,UAAM,MAAM,MAAM,GAAG,QAAQ,aAAa,EAAE,IAAI,OAAO,OAAO,UAAU,OAAO,UAAU,gBAAgB,OAAO,eAAe,CAAC;AAChI,QAAI,CAAC,IAAK,OAAM,SAAS,eAAe;AACxC,UAAM,SAAS,MAAM,sBAAsB,IAAI,OAAO,UAAU,EAAE,UAAU,OAAO,UAAU,gBAAgB,OAAO,eAAe,GAAG,QAAW,oBAAoB;AACrK,oBAAgB,QAAQ,OAAO,gBAAgB,OAAO,QAAQ;AAC9D,UAAM,SAAS,MAAM,iBAAiB,IAAI,MAAM;AAC9C,QAAI,OAAO,SAAS,OAAO,KAAK,GAAG;AACjC,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,uBAAuB,CAAC;AAAA,IAChE;AAEA,UAAM,aAAa,GAAG,OAAO,uBAAuB;AAAA,MAClD;AAAA,MACA;AAAA,MACA,gBAAgB,OAAO;AAAA,MACvB,UAAU,OAAO;AAAA,IACnB,CAAC;AACD,OAAG,QAAQ,UAAU;AACrB,UAAM,GAAG,MAAM;AAEf,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,OAAO,WAAW,EAAE;AAAA,QACxB,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,MACnB;AAAA,IACF,CAAC;AAED,UAAM,mBAAmB,0BAA0B;AAAA,MACjD,IAAI,OAAO,WAAW,EAAE;AAAA,MACxB,OAAO,OAAO;AAAA,MACd,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,UAAU,OAAO;AAAA,IACnB,GAAG,EAAE,YAAY,KAAK,CAAC;AAEvB,WAAO,EAAE,cAAc,WAAW,GAAG;AAAA,EACvC;AAAA,EACA,UAAU,OAAO,EAAE,QAAQ,IAAI,MAAM;AACnC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,aAAa,MAAM;AAAA,MACvB;AAAA,MACA;AAAA,MACA,EAAE,IAAI,OAAO,aAAa;AAAA,MAC1B,EAAE,UAAU,CAAC,OAAO,QAAQ,EAAE;AAAA,MAC9B,EAAE,UAAU,IAAI,MAAM,YAAY,MAAM,gBAAgB,IAAI,0BAA0B,IAAI,MAAM,SAAS,KAAK;AAAA,IAChH;AACA,QAAI,CAAC,WAAY,QAAO;AACxB,UAAM,QAAQ,OAAO,WAAW,QAAQ,WAAW,WAAW,MAAM,WAAW,IAAI;AACnF,UAAM,WAAW,OAAO,WAAW,WAAW,WAAW,WAAW,SAAS,WAAW,OAAO;AAC/F,WAAO;AAAA,MACL,aAAa,UAAU,+BAA+B,YAAY;AAAA,MAClE,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,UAAU,WAAW;AAAA,MACrB,gBAAgB,WAAW;AAAA,MAC3B,SAAS;AAAA,QACP,MAAM;AAAA,UACJ,QAAQ;AAAA,YACN;AAAA,YACA;AAAA,YACA,UAAU,WAAW;AAAA,YACrB,gBAAgB,WAAW;AAAA,UAC7B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,UAAU,mBAA6C,QAAQ;AACrE,UAAM,SAAS,SAAS;AACxB,QAAI,CAAC,OAAQ;AACb,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,GAAG,aAAa,uBAAuB;AAAA,MAC3C,KAAK,OAAO;AAAA,MACZ,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,UAAU,mBAA6C,QAAQ;AACrE,UAAM,SAAS,SAAS;AACxB,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,sDAAsD,CAAC;AAAA,IAC/F;AACA,UAAM,eAAe,UAAU,cAAc;AAC7C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,MAAM,MAAM,GAAG,QAAQ,aAAa,EAAE,IAAI,OAAO,MAAM,CAAC;AAC9D,QAAI,CAAC,IAAK,OAAM,SAAS,eAAe;AACxC,UAAM,SAAS,MAAM,sBAAsB,IAAI,OAAO,UAAU,EAAE,UAAU,OAAO,UAAU,gBAAgB,OAAO,eAAe,GAAG,QAAW,oBAAoB;AACrK,oBAAgB,QAAQ,OAAO,gBAAgB,OAAO,QAAQ;AAE9D,QAAI,aAAa,MAAM,GAAG,QAAQ,uBAAuB;AAAA,MACvD;AAAA,MACA;AAAA,MACA,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,IACzB,CAAC;AACD,QAAI,CAAC,YAAY;AACf,mBAAa,GAAG,OAAO,uBAAuB;AAAA,QAC5C,GAAI,eAAe,EAAE,IAAI,aAAa,IAAI,CAAC;AAAA,QAC3C;AAAA,QACA;AAAA,QACA,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,MACnB,CAAC;AACD,SAAG,QAAQ,UAAU;AACrB,YAAM,GAAG,MAAM;AAAA,IACjB;AAEA,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,OAAO,WAAW,EAAE;AAAA,QACxB,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,MACnB;AAAA,IACF,CAAC;AAED,UAAM,mBAAmB,0BAA0B;AAAA,MACjD,IAAI,OAAO,WAAW,EAAE;AAAA,MACxB,OAAO,OAAO;AAAA,MACd,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,UAAU,OAAO;AAAA,IACnB,GAAG,EAAE,YAAY,KAAK,CAAC;AAEvB,WAAO,EAAE,cAAc,WAAW,GAAG;AAAA,EACvC;AACF;AAEA,MAAM,qBAA0F;AAAA,EAC9F,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,SAAS,oBAAoB,MAAM,QAAQ;AACjD,sBAAkB,KAAK,OAAO,QAAQ;AACtC,4BAAwB,KAAK,OAAO,cAAc;AAElD,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,WAAW,MAAM,GAAG,QAAQ,uBAAuB;AAAA,MACvD,KAAK,OAAO;AAAA,MACZ,QAAQ,OAAO;AAAA,MACf,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,IACzB,CAAC;AACD,QAAI,CAAC,SAAU,OAAM,SAAS,0BAA0B;AACxD,UAAM,GAAG,OAAO,QAAQ,EAAE,MAAM;AAEhC,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,OAAO,SAAS,EAAE;AAAA,QACtB,gBAAgB,SAAS;AAAA,QACzB,UAAU,SAAS;AAAA,MACrB;AAAA,IACF,CAAC;AAED,UAAM,mBAAmB,yBAAyB;AAAA,MAChD,IAAI,OAAO,SAAS,EAAE;AAAA,MACtB,OAAO,OAAO;AAAA,MACd,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,UAAU,OAAO;AAAA,IACnB,GAAG,EAAE,YAAY,KAAK,CAAC;AAEvB,WAAO,EAAE,cAAc,SAAS,MAAM,KAAK;AAAA,EAC7C;AAAA,EACA,UAAU,OAAO,EAAE,WAAW,MAAM,MAAM;AACxC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,SAAS,oBAAoB,MAAM,KAAK;AAC9C,WAAO;AAAA,MACL,aAAa,UAAU,iCAAiC,cAAc;AAAA,MACtE,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,SAAS;AAAA,QACP,MAAM;AAAA,UACJ,QAAQ;AAAA,YACN,OAAO,OAAO;AAAA,YACd,UAAU,OAAO;AAAA,YACjB,UAAU,OAAO;AAAA,YACjB,gBAAgB,OAAO;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,UAAU,mBAA6C,QAAQ;AACrE,UAAM,SAAS,SAAS;AACxB,QAAI,CAAC,OAAQ;AACb,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,MAAM,MAAM,GAAG,QAAQ,aAAa,EAAE,IAAI,OAAO,MAAM,CAAC;AAC9D,UAAM,SAAS,MAAM,sBAAsB,IAAI,OAAO,UAAU,EAAE,UAAU,OAAO,UAAU,gBAAgB,OAAO,eAAe,GAAG,QAAW,oBAAoB;AACrK,oBAAgB,QAAQ,OAAO,gBAAgB,OAAO,QAAQ;AAC9D,QAAI,CAAC,IAAK,OAAM,SAAS,eAAe;AACxC,UAAM,WAAW,MAAM,GAAG,QAAQ,uBAAuB;AAAA,MACvD;AAAA,MACA;AAAA,MACA,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,IACzB,CAAC;AACD,QAAI,CAAC,UAAU;AACb,YAAM,aAAa,GAAG,OAAO,uBAAuB;AAAA,QAClD;AAAA,QACA;AAAA,QACA,UAAU,OAAO;AAAA,QACjB,gBAAgB,OAAO;AAAA,MACzB,CAAC;AACD,SAAG,QAAQ,UAAU;AACrB,YAAM,GAAG,MAAM;AAAA,IACjB;AAAA,EACF;AACF;AAEA,gBAAgB,gBAAgB;AAChC,gBAAgB,gBAAgB;AAChC,gBAAgB,gBAAgB;AAChC,gBAAgB,gBAAgB;AAChC,gBAAgB,kBAAkB;",
|
|
4
|
+
"sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId } from '@open-mercato/shared/lib/commands/helpers'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { LockMode, type FilterQuery } from '@mikro-orm/core'\nimport { buildScopedWhere } from '@open-mercato/shared/lib/api/crud'\nimport { isUnrestrictedOrganizationScope } from '@open-mercato/shared/lib/auth/organizationAccess'\nimport { CustomerEntity, CustomerTag, CustomerTagAssignment } from '../data/entities'\nimport {\n tagCreateSchema,\n tagUpdateSchema,\n tagAssignmentSchema,\n type TagCreateInput,\n type TagUpdateInput,\n type TagAssignmentInput,\n} from '../data/validators'\nimport {\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n requireCustomerEntity,\n ensureSameScope,\n loadEntityTagIds,\n emitQueryIndexUpsertEvents,\n} from './shared'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, forbidden, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'\nimport { emitCustomersEvent } from '../events'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'\nimport { E } from '#generated/entities.ids.generated'\n\n// The route (`api/tags/route.ts`) declares this same entity type and hands it down to the\n// execute path, but an undo runs through `CommandBus.undo()` where no route declaration is\n// active \u2014 so the undo marks carry the indexer explicitly, or a restored tag would stay\n// invisible to every index-backed list until the next full rebuild (#5741).\nconst tagCrudIndexer: CrudIndexerConfig<CustomerTag> = {\n entityType: E.customers.customer_tag,\n}\n\nconst tagCrudEvents: CrudEventsConfig = {\n module: 'customers',\n entity: 'tag',\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 TagSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n slug: string\n label: string\n color: string | null\n description: string | null\n assignments?: TagMembershipSnapshot[]\n}\n\ntype TagMembershipSnapshot = {\n id: string\n entityId: string\n organizationId: string\n tenantId: string\n createdAt: string\n}\n\ntype TagUndoPayload = {\n before?: TagSnapshot | null\n after?: TagSnapshot | null\n}\n\ntype TagAssignmentSnapshot = {\n tagId: string\n entityId: string\n organizationId: string\n tenantId: string\n}\n\ntype TagAssignmentUndoPayload = {\n before?: TagAssignmentSnapshot | null\n}\n\nfunction tagDeleteLookup(id: string, ctx: CommandRuntimeContext) {\n const isSuperAdmin = ctx.auth?.isSuperAdmin === true\n const organizationScope = ctx.organizationScope\n const tenantId = ctx.auth ? (ctx.auth.tenantId ?? null) : (organizationScope?.tenantId ?? null)\n if (ctx.auth && !isSuperAdmin && (!tenantId || (organizationScope && !organizationScope.tenantId))) {\n throw forbidden()\n }\n let organizationIds: string[] | undefined\n if (organizationScope) {\n if (!isUnrestrictedOrganizationScope({ isSuperAdmin, allowedOrganizationIds: organizationScope.allowedIds })) {\n organizationIds = organizationScope.allowedIds ?? []\n }\n } else if (!isSuperAdmin) {\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId\n if (organizationId) organizationIds = [organizationId]\n }\n return {\n where: buildScopedWhere({ id }, {\n tenantId: tenantId ?? undefined,\n organizationIds,\n softDeleteField: null,\n }) as FilterQuery<CustomerTag>,\n scope: { tenantId, organizationId: organizationIds?.length === 1 ? organizationIds[0] : null },\n }\n}\n\nasync function loadTagSnapshot(\n em: EntityManager,\n id: string,\n lookup?: ReturnType<typeof tagDeleteLookup>,\n): Promise<TagSnapshot | null> {\n const tag = lookup\n ? await findOneWithDecryption(em, CustomerTag, lookup.where, {}, lookup.scope)\n : await em.findOne(CustomerTag, { id })\n if (!tag) return null\n return {\n id: tag.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n slug: tag.slug,\n label: tag.label,\n color: tag.color ?? null,\n description: tag.description ?? null,\n }\n}\n\nasync function loadTagAssignments(em: EntityManager, tag: CustomerTag) {\n const scope = { tenantId: tag.tenantId, organizationId: tag.organizationId }\n return findWithDecryption(em, CustomerTagAssignment, { tag: tag.id, ...scope }, {}, scope)\n}\n\nasync function refreshTagMemberships(\n ctx: CommandRuntimeContext,\n tag: CustomerTag,\n assignments: CustomerTagAssignment[],\n action: 'created' | 'deleted',\n) {\n const dataEngine = ctx.container.resolve<DataEngine>('dataEngine')\n for (const assignment of assignments) {\n await emitCrudSideEffects({\n dataEngine,\n action,\n entity: assignment,\n identifiers: { id: assignment.id, tenantId: tag.tenantId, organizationId: tag.organizationId },\n indexer: { entityType: E.customers.customer_tag_assignment },\n })\n await emitCustomersEvent(action === 'created' ? 'customers.tag.assigned' : 'customers.tag.removed', {\n id: assignment.id,\n tagId: tag.id,\n entityId: assignment.entity.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n }, { persistent: true })\n }\n await emitQueryIndexUpsertEvents(ctx, assignments.map((assignment) => ({\n entityType: E.customers.customer_entity,\n recordId: assignment.entity.id,\n tenantId: tag.tenantId,\n organizationId: tag.organizationId,\n })))\n await invalidateCrudCache(ctx.container, 'customers.tagAssignment', {\n tenantId: tag.tenantId,\n organizationId: tag.organizationId,\n }, tag.tenantId, `customers.tags.delete.${action}`, ['customers.person', 'customers.company'])\n}\n\nconst createTagCommand: CommandHandler<TagCreateInput, { tagId: string }> = {\n id: 'customers.tags.create',\n async execute(rawInput, ctx) {\n const parsed = tagCreateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const duplicate = await em.findOne(CustomerTag, {\n slug: parsed.slug,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n })\n if (duplicate) throw new CrudHttpError(409, { error: 'Tag slug already exists' })\n\n const tag = em.create(CustomerTag, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n slug: parsed.slug,\n label: parsed.label,\n color: parsed.color ?? null,\n description: parsed.description ?? null,\n })\n em.persist(tag)\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: tag,\n identifiers: {\n id: tag.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n },\n events: tagCrudEvents,\n })\n\n return { tagId: tag.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadTagSnapshot(em, result.tagId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadTagSnapshot(em, result.tagId)\n return {\n actionLabel: translate('customers.audit.tags.create', 'Create tag'),\n resourceKind: 'customers.tag',\n resourceId: result.tagId,\n tenantId: snapshot?.tenantId ?? null,\n organizationId: snapshot?.organizationId ?? null,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies TagUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const tagId = logEntry?.resourceId ?? null\n if (!tagId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id: tagId })\n if (tag) {\n await em.nativeDelete(CustomerTagAssignment, { tag })\n em.remove(tag)\n await em.flush()\n }\n },\n redo: makeCreateRedo<CustomerTag, TagSnapshot, TagCreateInput, { tagId: string }>({\n entityClass: CustomerTag,\n buildResult: (entity) => ({ tagId: entity.id }),\n events: tagCrudEvents,\n }),\n}\n\nconst updateTagCommand: CommandHandler<TagUpdateInput, { tagId: string }> = {\n id: 'customers.tags.update',\n async prepare(rawInput, ctx) {\n const parsed = tagUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadTagSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const parsed = tagUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id: parsed.id })\n if (!tag) throw notFound('Tag not found')\n ensureTenantScope(ctx, tag.tenantId)\n ensureOrganizationScope(ctx, tag.organizationId)\n\n if (parsed.slug !== undefined && parsed.slug !== tag.slug) {\n const duplicate = await em.findOne(CustomerTag, {\n slug: parsed.slug,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n id: { $ne: tag.id },\n })\n if (duplicate) throw new CrudHttpError(409, { error: 'Tag slug already exists' })\n tag.slug = parsed.slug\n }\n if (parsed.label !== undefined) tag.label = parsed.label\n if (parsed.color !== undefined) tag.color = parsed.color ?? null\n if (parsed.description !== undefined) tag.description = parsed.description ?? null\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: tag,\n identifiers: {\n id: tag.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n },\n events: tagCrudEvents,\n })\n\n return { tagId: tag.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as TagSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadTagSnapshot(em, before.id)\n const changes =\n afterSnapshot && before\n ? buildChanges(\n before as unknown as Record<string, unknown>,\n afterSnapshot as unknown as Record<string, unknown>,\n ['slug', 'label', 'color', 'description']\n )\n : {}\n return {\n actionLabel: translate('customers.audit.tags.update', 'Update tag'),\n resourceKind: 'customers.tag',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies TagUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TagUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let tag = await em.findOne(CustomerTag, { id: before.id })\n if (!tag) {\n tag = em.create(CustomerTag, {\n id: before.id,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n slug: before.slug,\n label: before.label,\n color: before.color,\n description: before.description,\n })\n em.persist(tag)\n } else {\n tag.slug = before.slug\n tag.label = before.label\n tag.color = before.color\n tag.description = before.description\n }\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: tag,\n identifiers: {\n id: tag.id,\n organizationId: tag.organizationId,\n tenantId: tag.tenantId,\n },\n events: tagCrudEvents,\n indexer: tagCrudIndexer,\n })\n },\n}\n\nconst deleteTagCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { tagId: string }> = {\n id: 'customers.tags.delete',\n prepareSnapshotInsideTransaction: true,\n async prepare(input, ctx) {\n const id = requireId(input, 'Tag id required')\n const em = ctx.transactionalEm ?? (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadTagSnapshot(em, id, tagDeleteLookup(id, ctx))\n if (!snapshot) return {}\n ensureTenantScope(ctx, snapshot.tenantId)\n ensureOrganizationScope(ctx, snapshot.organizationId)\n const assignments = await findWithDecryption(em, CustomerTagAssignment, {\n tag: id,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n }, {}, snapshot)\n snapshot.assignments = assignments.map((assignment) => ({\n id: assignment.id,\n entityId: assignment.entity.id,\n tenantId: assignment.tenantId,\n organizationId: assignment.organizationId,\n createdAt: assignment.createdAt.toISOString(),\n }))\n return { before: snapshot }\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Tag id required')\n const lookup = tagDeleteLookup(id, ctx)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let tag: CustomerTag | null = null\n let assignments: CustomerTagAssignment[] = []\n await withAtomicFlush(em, [\n async () => {\n tag = await findOneWithDecryption(em, CustomerTag, lookup.where, { lockMode: LockMode.PESSIMISTIC_WRITE }, lookup.scope)\n if (!tag) throw notFound('Tag not found')\n ensureTenantScope(ctx, tag.tenantId)\n ensureOrganizationScope(ctx, tag.organizationId)\n await ctx.beforeTransactionalWrite?.(em, input)\n assignments = await loadTagAssignments(em, tag)\n await em.nativeDelete(CustomerTagAssignment, { tag, tenantId: tag.tenantId, organizationId: tag.organizationId })\n em.remove(tag)\n },\n ], { transaction: true, label: 'customers.tags.delete' })\n if (!tag) throw notFound('Tag not found')\n const deletedTag = tag as CustomerTag\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: deletedTag,\n identifiers: {\n id: deletedTag.id,\n organizationId: deletedTag.organizationId,\n tenantId: deletedTag.tenantId,\n },\n events: tagCrudEvents,\n indexer: tagCrudIndexer,\n })\n await refreshTagMemberships(ctx, deletedTag, assignments, 'deleted')\n return { tagId: deletedTag.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as TagSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('customers.audit.tags.delete', 'Delete tag'),\n resourceKind: 'customers.tag',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n context: { cacheAliases: ['customers.tagAssignment', 'customers.person', 'customers.company'] },\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n } satisfies TagUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TagUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n ensureTenantScope(ctx, before.tenantId)\n ensureOrganizationScope(ctx, before.organizationId)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = { tenantId: before.tenantId, organizationId: before.organizationId }\n const existingTag = await findOneWithDecryption(em, CustomerTag, { id: before.id, ...scope }, {}, scope)\n const tag = existingTag ?? em.create(CustomerTag, { id: before.id, ...scope, slug: before.slug, label: before.label })\n const restoredAssignments: CustomerTagAssignment[] = []\n await withAtomicFlush(em, [\n () => {\n tag.slug = before.slug\n tag.label = before.label\n tag.color = before.color\n tag.description = before.description\n em.persist(tag)\n },\n async () => {\n const assignments = (before.assignments ?? []).filter((assignment) =>\n assignment.tenantId === before.tenantId && assignment.organizationId === before.organizationId,\n )\n if (!assignments.length) return\n const customers = await findWithDecryption(em, CustomerEntity, {\n id: { $in: assignments.map((assignment) => assignment.entityId) },\n ...scope,\n deletedAt: null,\n kind: { $in: ['person', 'company'] },\n }, {}, scope)\n const existingAssignments = await loadTagAssignments(em, tag)\n const assignedEntityIds = new Set(existingAssignments.map((assignment) => assignment.entity.id))\n const customersById = new Map(customers.map((customer) => [customer.id, customer]))\n for (const snapshot of assignments) {\n const entity = customersById.get(snapshot.entityId)\n if (!entity || assignedEntityIds.has(entity.id)) continue\n const assignment = em.create(CustomerTagAssignment, {\n id: snapshot.id,\n tag,\n entity,\n ...scope,\n createdAt: new Date(snapshot.createdAt),\n })\n em.persist(assignment)\n restoredAssignments.push(assignment)\n assignedEntityIds.add(entity.id)\n }\n },\n ], { transaction: true, label: 'customers.tags.delete.undo' })\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'created',\n entity: tag,\n identifiers: {\n id: before.id,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n },\n events: tagCrudEvents,\n indexer: tagCrudIndexer,\n })\n await refreshTagMemberships(ctx, tag, restoredAssignments, 'created')\n },\n}\n\nconst assignTagCommand: CommandHandler<TagAssignmentInput, { assignmentId: string }> = {\n id: 'customers.tags.assign',\n async execute(rawInput, ctx) {\n const parsed = tagAssignmentSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id: parsed.tagId, tenantId: parsed.tenantId, organizationId: parsed.organizationId })\n if (!tag) throw notFound('Tag not found')\n const entity = await requireCustomerEntity(em, parsed.entityId, { tenantId: parsed.tenantId, organizationId: parsed.organizationId }, undefined, 'Customer not found')\n ensureSameScope(entity, parsed.organizationId, parsed.tenantId)\n const tagIds = await loadEntityTagIds(em, entity)\n if (tagIds.includes(parsed.tagId)) {\n throw new CrudHttpError(409, { error: 'Tag already assigned' })\n }\n\n const assignment = em.create(CustomerTagAssignment, {\n tag,\n entity,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n })\n em.persist(assignment)\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: assignment,\n identifiers: {\n id: String(assignment.id),\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n },\n })\n\n await emitCustomersEvent('customers.tag.assigned', {\n id: String(assignment.id),\n tagId: parsed.tagId,\n entityId: parsed.entityId,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n }, { persistent: true })\n\n return { assignmentId: assignment.id }\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const assignment = await findOneWithDecryption(\n em,\n CustomerTagAssignment,\n { id: result.assignmentId },\n { populate: ['tag', 'entity'] },\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null },\n )\n if (!assignment) return null\n const tagId = typeof assignment.tag === 'string' ? assignment.tag : assignment.tag.id\n const entityId = typeof assignment.entity === 'string' ? assignment.entity : assignment.entity.id\n return {\n actionLabel: translate('customers.audit.tags.assign', 'Assign tag'),\n resourceKind: 'customers.tagAssignment',\n resourceId: result.assignmentId,\n tenantId: assignment.tenantId,\n organizationId: assignment.organizationId,\n payload: {\n undo: {\n before: {\n tagId,\n entityId,\n tenantId: assignment.tenantId,\n organizationId: assignment.organizationId,\n } satisfies TagAssignmentSnapshot,\n } satisfies TagAssignmentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TagAssignmentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await em.nativeDelete(CustomerTagAssignment, {\n tag: before.tagId,\n entity: before.entityId,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n })\n },\n redo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TagAssignmentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) {\n throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for tag assign' })\n }\n const assignmentId = logEntry?.resourceId ?? null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id: before.tagId })\n if (!tag) throw notFound('Tag not found')\n const entity = await requireCustomerEntity(em, before.entityId, { tenantId: before.tenantId, organizationId: before.organizationId }, undefined, 'Customer not found')\n ensureSameScope(entity, before.organizationId, before.tenantId)\n\n let assignment = await em.findOne(CustomerTagAssignment, {\n tag,\n entity,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n })\n if (!assignment) {\n assignment = em.create(CustomerTagAssignment, {\n ...(assignmentId ? { id: assignmentId } : {}),\n tag,\n entity,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n })\n em.persist(assignment)\n await em.flush()\n }\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: assignment,\n identifiers: {\n id: String(assignment.id),\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n },\n })\n\n await emitCustomersEvent('customers.tag.assigned', {\n id: String(assignment.id),\n tagId: before.tagId,\n entityId: before.entityId,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n }, { persistent: true })\n\n return { assignmentId: assignment.id }\n },\n}\n\nconst unassignTagCommand: CommandHandler<TagAssignmentInput, { assignmentId: string | null }> = {\n id: 'customers.tags.unassign',\n async execute(rawInput, ctx) {\n const parsed = tagAssignmentSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await em.findOne(CustomerTagAssignment, {\n tag: parsed.tagId,\n entity: parsed.entityId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n })\n if (!existing) throw notFound('Tag assignment not found')\n await em.remove(existing).flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: existing,\n identifiers: {\n id: String(existing.id),\n organizationId: existing.organizationId,\n tenantId: existing.tenantId,\n },\n })\n\n await emitCustomersEvent('customers.tag.removed', {\n id: String(existing.id),\n tagId: parsed.tagId,\n entityId: parsed.entityId,\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n }, { persistent: true })\n\n return { assignmentId: existing.id ?? null }\n },\n buildLog: async ({ snapshots, input }) => {\n const { translate } = await resolveTranslations()\n const parsed = tagAssignmentSchema.parse(input)\n return {\n actionLabel: translate('customers.audit.tags.unassign', 'Unassign tag'),\n resourceKind: 'customers.tagAssignment',\n resourceId: parsed.tagId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n payload: {\n undo: {\n before: {\n tagId: parsed.tagId,\n entityId: parsed.entityId,\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n } satisfies TagAssignmentSnapshot,\n } satisfies TagAssignmentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TagAssignmentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const tag = await em.findOne(CustomerTag, { id: before.tagId })\n const entity = await requireCustomerEntity(em, before.entityId, { tenantId: before.tenantId, organizationId: before.organizationId }, undefined, 'Customer not found')\n ensureSameScope(entity, before.organizationId, before.tenantId)\n if (!tag) throw notFound('Tag not found')\n const existing = await em.findOne(CustomerTagAssignment, {\n tag,\n entity,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n })\n if (!existing) {\n const assignment = em.create(CustomerTagAssignment, {\n tag,\n entity,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n })\n em.persist(assignment)\n await em.flush()\n }\n },\n}\n\nregisterCommand(createTagCommand)\nregisterCommand(updateTagCommand)\nregisterCommand(deleteTagCommand)\nregisterCommand(assignTagCommand)\nregisterCommand(unassignTagCommand)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.10.1-develop.
|
|
3
|
+
"version": "0.10.1-develop.7672.1.c2e4c0b5df",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -265,16 +265,16 @@
|
|
|
265
265
|
"zod": "^4.4.3"
|
|
266
266
|
},
|
|
267
267
|
"peerDependencies": {
|
|
268
|
-
"@open-mercato/ai-assistant": "0.10.1-develop.
|
|
269
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
270
|
-
"@open-mercato/ui": "0.10.1-develop.
|
|
268
|
+
"@open-mercato/ai-assistant": "0.10.1-develop.7672.1.c2e4c0b5df",
|
|
269
|
+
"@open-mercato/shared": "0.10.1-develop.7672.1.c2e4c0b5df",
|
|
270
|
+
"@open-mercato/ui": "0.10.1-develop.7672.1.c2e4c0b5df",
|
|
271
271
|
"react": "^19.0.0",
|
|
272
272
|
"react-dom": "^19.0.0"
|
|
273
273
|
},
|
|
274
274
|
"devDependencies": {
|
|
275
|
-
"@open-mercato/ai-assistant": "0.10.1-develop.
|
|
276
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
277
|
-
"@open-mercato/ui": "0.10.1-develop.
|
|
275
|
+
"@open-mercato/ai-assistant": "0.10.1-develop.7672.1.c2e4c0b5df",
|
|
276
|
+
"@open-mercato/shared": "0.10.1-develop.7672.1.c2e4c0b5df",
|
|
277
|
+
"@open-mercato/ui": "0.10.1-develop.7672.1.c2e4c0b5df",
|
|
278
278
|
"@testing-library/dom": "^10.4.1",
|
|
279
279
|
"@testing-library/jest-dom": "^7.0.1",
|
|
280
280
|
"@testing-library/react": "^16.3.3",
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { registerCommand } from '@open-mercato/shared/lib/commands'
|
|
2
|
-
import type { CommandHandler } from '@open-mercato/shared/lib/commands'
|
|
2
|
+
import type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
3
3
|
import { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId } from '@open-mercato/shared/lib/commands/helpers'
|
|
4
4
|
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
5
5
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
6
|
-
import {
|
|
6
|
+
import { LockMode, type FilterQuery } from '@mikro-orm/core'
|
|
7
|
+
import { buildScopedWhere } from '@open-mercato/shared/lib/api/crud'
|
|
8
|
+
import { isUnrestrictedOrganizationScope } from '@open-mercato/shared/lib/auth/organizationAccess'
|
|
9
|
+
import { CustomerEntity, CustomerTag, CustomerTagAssignment } from '../data/entities'
|
|
7
10
|
import {
|
|
8
11
|
tagCreateSchema,
|
|
9
12
|
tagUpdateSchema,
|
|
@@ -19,11 +22,13 @@ import {
|
|
|
19
22
|
requireCustomerEntity,
|
|
20
23
|
ensureSameScope,
|
|
21
24
|
loadEntityTagIds,
|
|
25
|
+
emitQueryIndexUpsertEvents,
|
|
22
26
|
} from './shared'
|
|
23
27
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
24
|
-
import { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
|
|
28
|
+
import { CrudHttpError, forbidden, notFound } from '@open-mercato/shared/lib/crud/errors'
|
|
25
29
|
import type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'
|
|
26
|
-
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
30
|
+
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
31
|
+
import { invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'
|
|
27
32
|
import { emitCustomersEvent } from '../events'
|
|
28
33
|
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
29
34
|
import { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'
|
|
@@ -56,6 +61,15 @@ type TagSnapshot = {
|
|
|
56
61
|
label: string
|
|
57
62
|
color: string | null
|
|
58
63
|
description: string | null
|
|
64
|
+
assignments?: TagMembershipSnapshot[]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type TagMembershipSnapshot = {
|
|
68
|
+
id: string
|
|
69
|
+
entityId: string
|
|
70
|
+
organizationId: string
|
|
71
|
+
tenantId: string
|
|
72
|
+
createdAt: string
|
|
59
73
|
}
|
|
60
74
|
|
|
61
75
|
type TagUndoPayload = {
|
|
@@ -74,8 +88,40 @@ type TagAssignmentUndoPayload = {
|
|
|
74
88
|
before?: TagAssignmentSnapshot | null
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
|
|
78
|
-
const
|
|
91
|
+
function tagDeleteLookup(id: string, ctx: CommandRuntimeContext) {
|
|
92
|
+
const isSuperAdmin = ctx.auth?.isSuperAdmin === true
|
|
93
|
+
const organizationScope = ctx.organizationScope
|
|
94
|
+
const tenantId = ctx.auth ? (ctx.auth.tenantId ?? null) : (organizationScope?.tenantId ?? null)
|
|
95
|
+
if (ctx.auth && !isSuperAdmin && (!tenantId || (organizationScope && !organizationScope.tenantId))) {
|
|
96
|
+
throw forbidden()
|
|
97
|
+
}
|
|
98
|
+
let organizationIds: string[] | undefined
|
|
99
|
+
if (organizationScope) {
|
|
100
|
+
if (!isUnrestrictedOrganizationScope({ isSuperAdmin, allowedOrganizationIds: organizationScope.allowedIds })) {
|
|
101
|
+
organizationIds = organizationScope.allowedIds ?? []
|
|
102
|
+
}
|
|
103
|
+
} else if (!isSuperAdmin) {
|
|
104
|
+
const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId
|
|
105
|
+
if (organizationId) organizationIds = [organizationId]
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
where: buildScopedWhere({ id }, {
|
|
109
|
+
tenantId: tenantId ?? undefined,
|
|
110
|
+
organizationIds,
|
|
111
|
+
softDeleteField: null,
|
|
112
|
+
}) as FilterQuery<CustomerTag>,
|
|
113
|
+
scope: { tenantId, organizationId: organizationIds?.length === 1 ? organizationIds[0] : null },
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function loadTagSnapshot(
|
|
118
|
+
em: EntityManager,
|
|
119
|
+
id: string,
|
|
120
|
+
lookup?: ReturnType<typeof tagDeleteLookup>,
|
|
121
|
+
): Promise<TagSnapshot | null> {
|
|
122
|
+
const tag = lookup
|
|
123
|
+
? await findOneWithDecryption(em, CustomerTag, lookup.where, {}, lookup.scope)
|
|
124
|
+
: await em.findOne(CustomerTag, { id })
|
|
79
125
|
if (!tag) return null
|
|
80
126
|
return {
|
|
81
127
|
id: tag.id,
|
|
@@ -88,6 +134,46 @@ async function loadTagSnapshot(em: EntityManager, id: string): Promise<TagSnapsh
|
|
|
88
134
|
}
|
|
89
135
|
}
|
|
90
136
|
|
|
137
|
+
async function loadTagAssignments(em: EntityManager, tag: CustomerTag) {
|
|
138
|
+
const scope = { tenantId: tag.tenantId, organizationId: tag.organizationId }
|
|
139
|
+
return findWithDecryption(em, CustomerTagAssignment, { tag: tag.id, ...scope }, {}, scope)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function refreshTagMemberships(
|
|
143
|
+
ctx: CommandRuntimeContext,
|
|
144
|
+
tag: CustomerTag,
|
|
145
|
+
assignments: CustomerTagAssignment[],
|
|
146
|
+
action: 'created' | 'deleted',
|
|
147
|
+
) {
|
|
148
|
+
const dataEngine = ctx.container.resolve<DataEngine>('dataEngine')
|
|
149
|
+
for (const assignment of assignments) {
|
|
150
|
+
await emitCrudSideEffects({
|
|
151
|
+
dataEngine,
|
|
152
|
+
action,
|
|
153
|
+
entity: assignment,
|
|
154
|
+
identifiers: { id: assignment.id, tenantId: tag.tenantId, organizationId: tag.organizationId },
|
|
155
|
+
indexer: { entityType: E.customers.customer_tag_assignment },
|
|
156
|
+
})
|
|
157
|
+
await emitCustomersEvent(action === 'created' ? 'customers.tag.assigned' : 'customers.tag.removed', {
|
|
158
|
+
id: assignment.id,
|
|
159
|
+
tagId: tag.id,
|
|
160
|
+
entityId: assignment.entity.id,
|
|
161
|
+
organizationId: tag.organizationId,
|
|
162
|
+
tenantId: tag.tenantId,
|
|
163
|
+
}, { persistent: true })
|
|
164
|
+
}
|
|
165
|
+
await emitQueryIndexUpsertEvents(ctx, assignments.map((assignment) => ({
|
|
166
|
+
entityType: E.customers.customer_entity,
|
|
167
|
+
recordId: assignment.entity.id,
|
|
168
|
+
tenantId: tag.tenantId,
|
|
169
|
+
organizationId: tag.organizationId,
|
|
170
|
+
})))
|
|
171
|
+
await invalidateCrudCache(ctx.container, 'customers.tagAssignment', {
|
|
172
|
+
tenantId: tag.tenantId,
|
|
173
|
+
organizationId: tag.organizationId,
|
|
174
|
+
}, tag.tenantId, `customers.tags.delete.${action}`, ['customers.person', 'customers.company'])
|
|
175
|
+
}
|
|
176
|
+
|
|
91
177
|
const createTagCommand: CommandHandler<TagCreateInput, { tagId: string }> = {
|
|
92
178
|
id: 'customers.tags.create',
|
|
93
179
|
async execute(rawInput, ctx) {
|
|
@@ -290,39 +376,64 @@ const updateTagCommand: CommandHandler<TagUpdateInput, { tagId: string }> = {
|
|
|
290
376
|
|
|
291
377
|
const deleteTagCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { tagId: string }> = {
|
|
292
378
|
id: 'customers.tags.delete',
|
|
379
|
+
prepareSnapshotInsideTransaction: true,
|
|
293
380
|
async prepare(input, ctx) {
|
|
294
381
|
const id = requireId(input, 'Tag id required')
|
|
295
|
-
const em = (ctx.container.resolve('em') as EntityManager)
|
|
296
|
-
const snapshot = await loadTagSnapshot(em, id)
|
|
297
|
-
|
|
382
|
+
const em = ctx.transactionalEm ?? (ctx.container.resolve('em') as EntityManager).fork()
|
|
383
|
+
const snapshot = await loadTagSnapshot(em, id, tagDeleteLookup(id, ctx))
|
|
384
|
+
if (!snapshot) return {}
|
|
385
|
+
ensureTenantScope(ctx, snapshot.tenantId)
|
|
386
|
+
ensureOrganizationScope(ctx, snapshot.organizationId)
|
|
387
|
+
const assignments = await findWithDecryption(em, CustomerTagAssignment, {
|
|
388
|
+
tag: id,
|
|
389
|
+
tenantId: snapshot.tenantId,
|
|
390
|
+
organizationId: snapshot.organizationId,
|
|
391
|
+
}, {}, snapshot)
|
|
392
|
+
snapshot.assignments = assignments.map((assignment) => ({
|
|
393
|
+
id: assignment.id,
|
|
394
|
+
entityId: assignment.entity.id,
|
|
395
|
+
tenantId: assignment.tenantId,
|
|
396
|
+
organizationId: assignment.organizationId,
|
|
397
|
+
createdAt: assignment.createdAt.toISOString(),
|
|
398
|
+
}))
|
|
399
|
+
return { before: snapshot }
|
|
298
400
|
},
|
|
299
401
|
async execute(input, ctx) {
|
|
300
402
|
const id = requireId(input, 'Tag id required')
|
|
403
|
+
const lookup = tagDeleteLookup(id, ctx)
|
|
301
404
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
ensureTenantScope(ctx, tag.tenantId)
|
|
305
|
-
ensureOrganizationScope(ctx, tag.organizationId)
|
|
405
|
+
let tag: CustomerTag | null = null
|
|
406
|
+
let assignments: CustomerTagAssignment[] = []
|
|
306
407
|
await withAtomicFlush(em, [
|
|
307
408
|
async () => {
|
|
308
|
-
await em.
|
|
409
|
+
tag = await findOneWithDecryption(em, CustomerTag, lookup.where, { lockMode: LockMode.PESSIMISTIC_WRITE }, lookup.scope)
|
|
410
|
+
if (!tag) throw notFound('Tag not found')
|
|
411
|
+
ensureTenantScope(ctx, tag.tenantId)
|
|
412
|
+
ensureOrganizationScope(ctx, tag.organizationId)
|
|
413
|
+
await ctx.beforeTransactionalWrite?.(em, input)
|
|
414
|
+
assignments = await loadTagAssignments(em, tag)
|
|
415
|
+
await em.nativeDelete(CustomerTagAssignment, { tag, tenantId: tag.tenantId, organizationId: tag.organizationId })
|
|
309
416
|
em.remove(tag)
|
|
310
417
|
},
|
|
311
|
-
], { transaction: true })
|
|
418
|
+
], { transaction: true, label: 'customers.tags.delete' })
|
|
419
|
+
if (!tag) throw notFound('Tag not found')
|
|
420
|
+
const deletedTag = tag as CustomerTag
|
|
312
421
|
|
|
313
422
|
const de = (ctx.container.resolve('dataEngine') as DataEngine)
|
|
314
423
|
await emitCrudSideEffects({
|
|
315
424
|
dataEngine: de,
|
|
316
425
|
action: 'deleted',
|
|
317
|
-
entity:
|
|
426
|
+
entity: deletedTag,
|
|
318
427
|
identifiers: {
|
|
319
|
-
id:
|
|
320
|
-
organizationId:
|
|
321
|
-
tenantId:
|
|
428
|
+
id: deletedTag.id,
|
|
429
|
+
organizationId: deletedTag.organizationId,
|
|
430
|
+
tenantId: deletedTag.tenantId,
|
|
322
431
|
},
|
|
323
432
|
events: tagCrudEvents,
|
|
433
|
+
indexer: tagCrudIndexer,
|
|
324
434
|
})
|
|
325
|
-
|
|
435
|
+
await refreshTagMemberships(ctx, deletedTag, assignments, 'deleted')
|
|
436
|
+
return { tagId: deletedTag.id }
|
|
326
437
|
},
|
|
327
438
|
buildLog: async ({ snapshots }) => {
|
|
328
439
|
const before = snapshots.before as TagSnapshot | undefined
|
|
@@ -334,6 +445,7 @@ const deleteTagCommand: CommandHandler<{ body?: Record<string, unknown>; query?:
|
|
|
334
445
|
resourceId: before.id,
|
|
335
446
|
tenantId: before.tenantId,
|
|
336
447
|
organizationId: before.organizationId,
|
|
448
|
+
context: { cacheAliases: ['customers.tagAssignment', 'customers.person', 'customers.company'] },
|
|
337
449
|
snapshotBefore: before,
|
|
338
450
|
payload: {
|
|
339
451
|
undo: {
|
|
@@ -346,31 +458,51 @@ const deleteTagCommand: CommandHandler<{ body?: Record<string, unknown>; query?:
|
|
|
346
458
|
const payload = extractUndoPayload<TagUndoPayload>(logEntry)
|
|
347
459
|
const before = payload?.before
|
|
348
460
|
if (!before) return
|
|
461
|
+
ensureTenantScope(ctx, before.tenantId)
|
|
462
|
+
ensureOrganizationScope(ctx, before.organizationId)
|
|
349
463
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
organizationId: before.organizationId,
|
|
355
|
-
tenantId: before.tenantId,
|
|
356
|
-
slug: before.slug,
|
|
357
|
-
label: before.label,
|
|
358
|
-
color: before.color,
|
|
359
|
-
description: before.description,
|
|
360
|
-
})
|
|
361
|
-
em.persist(tag)
|
|
362
|
-
} else {
|
|
363
|
-
tag.slug = before.slug
|
|
364
|
-
tag.label = before.label
|
|
365
|
-
tag.color = before.color
|
|
366
|
-
tag.description = before.description
|
|
367
|
-
}
|
|
368
|
-
const restoredTag = tag
|
|
464
|
+
const scope = { tenantId: before.tenantId, organizationId: before.organizationId }
|
|
465
|
+
const existingTag = await findOneWithDecryption(em, CustomerTag, { id: before.id, ...scope }, {}, scope)
|
|
466
|
+
const tag = existingTag ?? em.create(CustomerTag, { id: before.id, ...scope, slug: before.slug, label: before.label })
|
|
467
|
+
const restoredAssignments: CustomerTagAssignment[] = []
|
|
369
468
|
await withAtomicFlush(em, [
|
|
370
469
|
() => {
|
|
371
|
-
|
|
470
|
+
tag.slug = before.slug
|
|
471
|
+
tag.label = before.label
|
|
472
|
+
tag.color = before.color
|
|
473
|
+
tag.description = before.description
|
|
474
|
+
em.persist(tag)
|
|
372
475
|
},
|
|
373
|
-
|
|
476
|
+
async () => {
|
|
477
|
+
const assignments = (before.assignments ?? []).filter((assignment) =>
|
|
478
|
+
assignment.tenantId === before.tenantId && assignment.organizationId === before.organizationId,
|
|
479
|
+
)
|
|
480
|
+
if (!assignments.length) return
|
|
481
|
+
const customers = await findWithDecryption(em, CustomerEntity, {
|
|
482
|
+
id: { $in: assignments.map((assignment) => assignment.entityId) },
|
|
483
|
+
...scope,
|
|
484
|
+
deletedAt: null,
|
|
485
|
+
kind: { $in: ['person', 'company'] },
|
|
486
|
+
}, {}, scope)
|
|
487
|
+
const existingAssignments = await loadTagAssignments(em, tag)
|
|
488
|
+
const assignedEntityIds = new Set(existingAssignments.map((assignment) => assignment.entity.id))
|
|
489
|
+
const customersById = new Map(customers.map((customer) => [customer.id, customer]))
|
|
490
|
+
for (const snapshot of assignments) {
|
|
491
|
+
const entity = customersById.get(snapshot.entityId)
|
|
492
|
+
if (!entity || assignedEntityIds.has(entity.id)) continue
|
|
493
|
+
const assignment = em.create(CustomerTagAssignment, {
|
|
494
|
+
id: snapshot.id,
|
|
495
|
+
tag,
|
|
496
|
+
entity,
|
|
497
|
+
...scope,
|
|
498
|
+
createdAt: new Date(snapshot.createdAt),
|
|
499
|
+
})
|
|
500
|
+
em.persist(assignment)
|
|
501
|
+
restoredAssignments.push(assignment)
|
|
502
|
+
assignedEntityIds.add(entity.id)
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
], { transaction: true, label: 'customers.tags.delete.undo' })
|
|
374
506
|
|
|
375
507
|
const de = (ctx.container.resolve('dataEngine') as DataEngine)
|
|
376
508
|
await emitCrudUndoSideEffects({
|
|
@@ -378,13 +510,14 @@ const deleteTagCommand: CommandHandler<{ body?: Record<string, unknown>; query?:
|
|
|
378
510
|
action: 'created',
|
|
379
511
|
entity: tag,
|
|
380
512
|
identifiers: {
|
|
381
|
-
id:
|
|
382
|
-
organizationId:
|
|
383
|
-
tenantId:
|
|
513
|
+
id: before.id,
|
|
514
|
+
organizationId: before.organizationId,
|
|
515
|
+
tenantId: before.tenantId,
|
|
384
516
|
},
|
|
385
517
|
events: tagCrudEvents,
|
|
386
518
|
indexer: tagCrudIndexer,
|
|
387
519
|
})
|
|
520
|
+
await refreshTagMemberships(ctx, tag, restoredAssignments, 'created')
|
|
388
521
|
},
|
|
389
522
|
}
|
|
390
523
|
|