@open-mercato/core 0.10.1-develop.7994.1.c9f31972aa → 0.10.1-develop.8022.1.b6753de892
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/audit_logs/components/AuditLogsActions.js +80 -50
- package/dist/modules/audit_logs/components/AuditLogsActions.js.map +3 -3
- package/dist/modules/audit_logs/lib/rowPresentation.js +82 -0
- package/dist/modules/audit_logs/lib/rowPresentation.js.map +7 -0
- package/dist/modules/catalog/commands/variants.js +89 -16
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/catalog/seed/examples.js +7 -0
- package/dist/modules/catalog/seed/examples.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +2 -2
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +10 -21
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260825140000_customers.js +17 -10
- package/dist/modules/customers/migrations/Migration20260825140000_customers.js.map +2 -2
- package/dist/modules/entities/cli.js +138 -66
- package/dist/modules/entities/cli.js.map +3 -3
- package/dist/modules/integrations/lib/state-service.js +1 -1
- package/dist/modules/integrations/lib/state-service.js.map +2 -2
- package/dist/modules/promotions/commands/codes.js +18 -3
- package/dist/modules/promotions/commands/codes.js.map +2 -2
- package/dist/modules/query_index/cli.js +17 -1
- package/dist/modules/query_index/cli.js.map +2 -2
- package/dist/modules/query_index/lib/reindexer.js +23 -5
- package/dist/modules/query_index/lib/reindexer.js.map +2 -2
- package/dist/modules/resources/commands/resource-types.js +42 -15
- package/dist/modules/resources/commands/resource-types.js.map +2 -2
- package/dist/modules/resources/commands/resources.js +54 -3
- package/dist/modules/resources/commands/resources.js.map +2 -2
- package/dist/modules/resources/lib/resourceTypeLock.js +15 -0
- package/dist/modules/resources/lib/resourceTypeLock.js.map +7 -0
- package/dist/modules/sales/commands/configuration.js +30 -16
- package/dist/modules/sales/commands/configuration.js.map +2 -2
- package/dist/modules/sales/components/documents/AddressesSection.js +36 -15
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/seed/examples.js +35 -6
- package/dist/modules/sales/seed/examples.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/audit_logs/components/AuditLogsActions.tsx +85 -48
- package/src/modules/audit_logs/i18n/de.json +16 -0
- package/src/modules/audit_logs/i18n/en.json +16 -0
- package/src/modules/audit_logs/i18n/es.json +16 -0
- package/src/modules/audit_logs/i18n/ko.json +16 -0
- package/src/modules/audit_logs/i18n/pl.json +16 -0
- package/src/modules/audit_logs/lib/rowPresentation.ts +92 -0
- package/src/modules/catalog/commands/variants.ts +96 -17
- package/src/modules/catalog/seed/examples.ts +7 -0
- package/src/modules/customers/api/interactions/route.ts +2 -2
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +9 -12
- package/src/modules/customers/i18n/de.json +2 -1
- package/src/modules/customers/i18n/en.json +2 -1
- package/src/modules/customers/i18n/es.json +2 -1
- package/src/modules/customers/i18n/ko.json +2 -1
- package/src/modules/customers/i18n/pl.json +2 -1
- package/src/modules/customers/migrations/Migration20260825140000_customers.ts +21 -10
- package/src/modules/entities/cli.ts +144 -65
- package/src/modules/integrations/lib/state-service.ts +1 -1
- package/src/modules/promotions/commands/codes.ts +19 -4
- package/src/modules/query_index/cli.ts +17 -1
- package/src/modules/query_index/lib/reindexer.ts +55 -6
- package/src/modules/resources/commands/resource-types.ts +47 -15
- package/src/modules/resources/commands/resources.ts +74 -3
- package/src/modules/resources/i18n/de.json +3 -0
- package/src/modules/resources/i18n/en.json +3 -0
- package/src/modules/resources/i18n/es.json +3 -0
- package/src/modules/resources/i18n/ko.json +3 -0
- package/src/modules/resources/i18n/pl.json +3 -0
- package/src/modules/resources/lib/resourceTypeLock.ts +14 -0
- package/src/modules/sales/commands/configuration.ts +35 -16
- package/src/modules/sales/components/documents/AddressesSection.tsx +51 -20
- package/src/modules/sales/i18n/de.json +3 -0
- package/src/modules/sales/i18n/en.json +3 -0
- package/src/modules/sales/i18n/es.json +3 -0
- package/src/modules/sales/i18n/ko.json +3 -0
- package/src/modules/sales/i18n/pl.json +4 -1
- package/src/modules/sales/seed/examples.ts +41 -6
|
@@ -8,6 +8,7 @@ import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
|
|
|
8
8
|
import { CrudHttpError, notFound } from "@open-mercato/shared/lib/crud/errors";
|
|
9
9
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
10
10
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
11
|
+
import { LockMode } from "@mikro-orm/core";
|
|
11
12
|
import { E } from "../../../generated/entities.ids.generated.js";
|
|
12
13
|
import {
|
|
13
14
|
Code,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
promotionCodeUpdateSchema,
|
|
23
24
|
promotionCodeDeleteSchema
|
|
24
25
|
} from "../data/validators.js";
|
|
26
|
+
import { hasRemainingGlobalUses } from "../lib/code-service.js";
|
|
25
27
|
const codesLogger = createLogger("promotions").child({ component: "code-commands" });
|
|
26
28
|
function resolveActorScope(ctx) {
|
|
27
29
|
const tenantId = ctx.auth?.tenantId ?? null;
|
|
@@ -111,6 +113,11 @@ function getCodeIdentifiers(code) {
|
|
|
111
113
|
async function loadCode(em, id) {
|
|
112
114
|
return em.findOne(Code, { id, deletedAt: null });
|
|
113
115
|
}
|
|
116
|
+
function resolveUndoActiveFlag(code, before) {
|
|
117
|
+
if (!before.active || code.type !== "static") return before.active;
|
|
118
|
+
const redeemedSinceUpdate = code.used > before.used;
|
|
119
|
+
return !redeemedSinceUpdate || hasRemainingGlobalUses(code);
|
|
120
|
+
}
|
|
114
121
|
function resolveCodeActorScope(ctx) {
|
|
115
122
|
const tenantId = ctx.auth?.tenantId ?? null;
|
|
116
123
|
const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
|
|
@@ -281,12 +288,19 @@ const updateCodeCommand = {
|
|
|
281
288
|
const before = payload?.before;
|
|
282
289
|
if (!before) return;
|
|
283
290
|
const em = ctx.container.resolve("em").fork();
|
|
284
|
-
|
|
285
|
-
if (!code) return;
|
|
291
|
+
let code = null;
|
|
286
292
|
await withAtomicFlush(
|
|
287
293
|
em,
|
|
288
294
|
[
|
|
295
|
+
async () => {
|
|
296
|
+
code = await em.findOne(
|
|
297
|
+
Code,
|
|
298
|
+
{ id: before.id, tenantId: before.tenantId, organizationId: before.organizationId },
|
|
299
|
+
{ lockMode: LockMode.PESSIMISTIC_WRITE }
|
|
300
|
+
);
|
|
301
|
+
},
|
|
289
302
|
() => {
|
|
303
|
+
if (!code) return;
|
|
290
304
|
code.name = before.name;
|
|
291
305
|
code.type = before.type;
|
|
292
306
|
code.code = before.code;
|
|
@@ -297,11 +311,12 @@ const updateCodeCommand = {
|
|
|
297
311
|
code.usage = before.usage;
|
|
298
312
|
code.usageAmount = before.usageAmount;
|
|
299
313
|
code.usagePerCustomer = before.usagePerCustomer;
|
|
300
|
-
code.active = before
|
|
314
|
+
code.active = resolveUndoActiveFlag(code, before);
|
|
301
315
|
}
|
|
302
316
|
],
|
|
303
317
|
{ transaction: true, label: "promotions.code.update.undo" }
|
|
304
318
|
);
|
|
319
|
+
if (!code) return;
|
|
305
320
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
306
321
|
await emitCrudUndoSideEffects({
|
|
307
322
|
dataEngine,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/promotions/commands/codes.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { runCrudCommandWrite } from '@open-mercato/shared/lib/commands/runCrudCommandWrite'\nimport { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n Code,\n GeneratedCode,\n type PromotionCodeType,\n type PromotionCodeUsageMode,\n} from '../data/entities'\nimport { assertPromotionScopeOrNotFound } from './shared'\nimport {\n codeReservationReleaseSchema,\n codeReserveInputSchema,\n codeUseInputSchema,\n promotionCodeCreateSchema,\n promotionCodeUpdateSchema,\n promotionCodeDeleteSchema,\n type PromotionCodeCreateInput,\n type PromotionCodeUpdateInput,\n type PromotionCodeDeleteInput,\n} from '../data/validators'\nimport { PromotionCodeService, type PromotionsCodeScope } from '../lib/code-service'\nimport type { PromotionCodeGenerationService } from '../services/code-generation'\nimport type { CodeReservationResult, CodeUseResult } from '../lib/types'\n\nconst codesLogger = createLogger('promotions').child({ component: 'code-commands' })\n\n// SPEC-055 \u00A7Amendment A.2a: `reserveCode`, `releaseCode` and `useCode` are \"financially\n// or budget consequential writes\" and go through the command pattern the same as\n// `Promotion`/`Code` CRUD. `validateCode`/`extendReservation` are deliberately NOT here\n// \u2014 \u00A7A.2a names exactly five commands for the whole of Phase 3\n// (`reserve`/`release`/`use`/`usage.register`/`usage.revert`; the last two are Step\n// 6.3's), and the spec explicitly calls `validateCode`/`extendReservation` plain\n// reads/housekeeping with no mutation to command-wrap.\n//\n// Each command wraps `lib/code-service.ts`'s `PromotionCodeService` \u2014 the actual\n// resolution/locking/transaction logic lives there (\u00A7Code Lifecycle,\n// \u00A7Promotion Usage Lifecycle's row-lock correction applied to `codes/use`); this file\n// only resolves the command-runtime scope/EntityManager and dispatches to it, same split\n// as `commands/promotions.ts`'s thin command bodies around `data/validators.ts` schemas.\n\n// Mirrors `commands/promotions.ts`'s `resolveActorScope` \u2014 a code operation's\n// tenant/organization scope is always the caller's own actor scope (never a\n// client-supplied value), resolved from `ctx.auth`/`ctx.selectedOrganizationId`.\nfunction resolveActorScope(ctx: CommandRuntimeContext): PromotionsCodeScope {\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, {\n error: '[internal] Tenant and organization scope are required for a promotion code operation',\n })\n }\n ensureTenantScope(ctx, tenantId)\n ensureOrganizationScope(ctx, organizationId)\n return { tenantId, organizationId }\n}\n\n// `ctx.transactionalEm`, when set, is an already-open (possibly ambient \u2014 e.g.\n// `checkout`'s own) transaction this command MUST reuse rather than opening its own\n// (\u00A7Promotion Usage Lifecycle's row-lock correction depends on this for `codes/use`:\n// locking inside the SAME transaction as the caller is what makes the lock effective\n// whether the call is top-level or nested). Otherwise fork the request `em`, same\n// precedent as every other command in this module.\nfunction resolveCommandEm(ctx: CommandRuntimeContext): EntityManager {\n return ctx.transactionalEm ?? (ctx.container.resolve('em') as EntityManager).fork()\n}\n\nfunction resolveCodeService(ctx: CommandRuntimeContext): PromotionCodeService {\n return ctx.container.resolve('promotionCodeService') as PromotionCodeService\n}\n\nconst reserveCodeCommand: CommandHandler<unknown, CodeReservationResult> = {\n id: 'promotions.code.reserve',\n async execute(rawInput, ctx) {\n const parsed = codeReserveInputSchema.parse(rawInput)\n const scope = resolveActorScope(ctx)\n const em = resolveCommandEm(ctx)\n return resolveCodeService(ctx).reserveCode(em, parsed, scope)\n },\n}\n\nconst releaseCodeCommand: CommandHandler<unknown, { ok: true }> = {\n id: 'promotions.code.release',\n async execute(rawInput, ctx) {\n const parsed = codeReservationReleaseSchema.parse(rawInput)\n const scope = resolveActorScope(ctx)\n const em = resolveCommandEm(ctx)\n await resolveCodeService(ctx).releaseCode(em, parsed.codeId, parsed.cartId, scope)\n return { ok: true }\n },\n}\n\nconst useCodeCommand: CommandHandler<unknown, CodeUseResult> = {\n id: 'promotions.code.use',\n async execute(rawInput, ctx) {\n const parsed = codeUseInputSchema.parse(rawInput)\n const scope = resolveActorScope(ctx)\n const em = resolveCommandEm(ctx)\n return resolveCodeService(ctx).useCode(em, parsed, scope)\n },\n}\n\n// ---------------------------------------------------------------------------\n// Code CRUD (\u00A7Implementation Plan Phase 3 Step 3 \"commands/codes.ts \u2014 code CRUD with\n// undo, events\" \u2014 not built by Step 6.2, which only covered the reserve/release/use\n// commands above; Release 4 PLAN.md Step 6.4 closes that gap since `api/codes/route.ts`\n// needs it). Mirrors `commands/promotions.ts`'s create/update/delete shape exactly \u2014\n// command-driven writes, `runCrudCommandWrite`/`withAtomicFlush` for atomicity,\n// `emitCrudSideEffects`/`emitCrudUndoSideEffects` wired to the already-declared\n// `promotions.code.created/updated/deleted` events (`events.ts`) \u2014 so `api/codes/route.ts`\n// can follow `api/route.ts`'s own `makeCrudRoute` template literally.\n// ---------------------------------------------------------------------------\n\nconst CODE_ENTITY_ID = E.promotions.code\n\nconst codeCrudIndexer: CrudIndexerConfig<Code> = {\n entityType: CODE_ENTITY_ID,\n}\n\nconst codeCrudEvents: CrudEventsConfig<Code> = {\n module: 'promotions',\n entity: 'code',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nconst codeCommandOutputSchema = z.object({ codeId: z.string().uuid() })\n\ntype CodeSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n name: string\n type: PromotionCodeType\n code: string | null\n prefix: string | null\n suffix: string | null\n length: number | null\n amount: number | null\n generated: number\n used: number\n usage: PromotionCodeUsageMode\n usageAmount: number | null\n usagePerCustomer: number | null\n active: boolean\n updatedAt: string\n}\n\n// Delete-only extension of `CodeSnapshot` (rev \u2014 the generic `prepare()` return type is\n// `{ before?: unknown }`, so any extra data a specific command's undo needs must live\n// INSIDE the snapshot object itself, not as a sibling key \u2014 see\n// `@open-mercato/shared/lib/commands/types.ts`'s `CommandLogBuilderArgs.snapshots` shape).\ntype CodeDeleteSnapshot = CodeSnapshot & { activeGeneratedCodeIds: string[] }\n\ntype CodeUndoPayload = {\n before?: CodeSnapshot | CodeDeleteSnapshot | null\n after?: CodeSnapshot | null\n}\n\nfunction toCodeSnapshot(code: Code): CodeSnapshot {\n return {\n id: code.id,\n organizationId: code.organizationId,\n tenantId: code.tenantId,\n name: code.name,\n type: code.type,\n code: code.code ?? null,\n prefix: code.prefix ?? null,\n suffix: code.suffix ?? null,\n length: code.length ?? null,\n amount: code.amount ?? null,\n generated: code.generated,\n used: code.used,\n usage: code.usage,\n usageAmount: code.usageAmount ?? null,\n usagePerCustomer: code.usagePerCustomer ?? null,\n active: code.active,\n updatedAt: code.updatedAt.toISOString(),\n }\n}\n\nfunction getCodeIdentifiers(code: Code) {\n return { id: code.id, organizationId: code.organizationId, tenantId: code.tenantId }\n}\n\nasync function loadCode(em: EntityManager, id: string): Promise<Code | null> {\n return em.findOne(Code, { id, deletedAt: null })\n}\n\n// Mirrors `commands/promotions.ts`'s `resolveActorScope` \u2014 a new code's scope is always\n// the caller's own actor scope, never a client-supplied value.\nfunction resolveCodeActorScope(ctx: CommandRuntimeContext): PromotionsCodeScope {\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, {\n error: '[internal] Tenant and organization scope are required to create a promotion code',\n })\n }\n return { tenantId, organizationId }\n}\n\nconst createCodeCommand: CommandHandler<PromotionCodeCreateInput, { codeId: string }> = {\n id: 'promotions.code.create',\n outputSchema: codeCommandOutputSchema,\n async execute(rawInput, ctx) {\n const parsed = promotionCodeCreateSchema.parse(rawInput)\n const scope = resolveCodeActorScope(ctx)\n ensureTenantScope(ctx, scope.tenantId)\n ensureOrganizationScope(ctx, scope.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = em.create(Code, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n name: parsed.name,\n type: parsed.type,\n code: parsed.code ?? null,\n prefix: parsed.prefix ?? null,\n suffix: parsed.suffix ?? null,\n length: parsed.length ?? null,\n amount: parsed.amount ?? null,\n usage: parsed.usage,\n usageAmount: parsed.usageAmount ?? null,\n usagePerCustomer: parsed.usagePerCustomer ?? null,\n active: parsed.active,\n })\n em.persist(code)\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: code,\n identifiers: getCodeIdentifiers(code),\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n })\n\n // \u00A7API Contracts `POST /api/promotions/codes`: \"for dynamic type, triggers\n // generation worker.\" Fired after the row has committed and its CRUD side effects\n // ran \u2014 a transient enqueue failure must not fail the create itself; the admin can\n // retry generation via `POST /codes/[id]/generate`.\n if (code.type === 'dynamic') {\n try {\n const generationService = ctx.container.resolve('promotionCodeGenerationService') as PromotionCodeGenerationService\n await generationService.triggerGeneration(code.id, scope)\n } catch (err) {\n codesLogger.warn('[internal] promotions.code.create failed to trigger code generation', { codeId: code.id, err })\n }\n }\n\n return { codeId: code.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, result.codeId)\n return code ? toCodeSnapshot(code) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const after = snapshots.after as CodeSnapshot | undefined\n return {\n actionLabel: translate('promotions.audit.code.create', 'Create promotional code'),\n resourceKind: 'promotions.code',\n resourceId: result.codeId,\n tenantId: after?.tenantId ?? null,\n organizationId: after?.organizationId ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { after: after ?? null } satisfies CodeUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CodeUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await em.findOne(Code, { id: after.id })\n if (!code) return\n\n // `promotion_generated_codes.code_id` carries `ON DELETE CASCADE` (\u00A7Data Models), so\n // removing the Code row also removes every generated code minted for it.\n em.remove(code)\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'deleted',\n entity: code,\n identifiers: getCodeIdentifiers(code),\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n })\n },\n}\n\nconst updateCodeCommand: CommandHandler<PromotionCodeUpdateInput, { codeId: string }> = {\n id: 'promotions.code.update',\n outputSchema: codeCommandOutputSchema,\n async prepare(rawInput, ctx) {\n const parsed = promotionCodeUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, parsed.id)\n return code ? { before: toCodeSnapshot(code) } : null\n },\n async execute(rawInput, ctx) {\n const parsed = promotionCodeUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, parsed.id)\n if (!code) throw notFound('Promotion code not found')\n assertPromotionScopeOrNotFound(ctx, code)\n\n await runCrudCommandWrite({\n ctx,\n em,\n entityId: CODE_ENTITY_ID,\n action: 'updated',\n scope: { tenantId: code.tenantId, organizationId: code.organizationId },\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n sideEffect: () => ({\n entity: code,\n identifiers: getCodeIdentifiers(code),\n }),\n phases: [\n () => {\n if (parsed.name !== undefined) code.name = parsed.name\n if (parsed.type !== undefined) code.type = parsed.type\n if (parsed.code !== undefined) code.code = parsed.code ?? null\n if (parsed.prefix !== undefined) code.prefix = parsed.prefix ?? null\n if (parsed.suffix !== undefined) code.suffix = parsed.suffix ?? null\n if (parsed.length !== undefined) code.length = parsed.length ?? null\n if (parsed.amount !== undefined) code.amount = parsed.amount ?? null\n if (parsed.usage !== undefined) code.usage = parsed.usage\n if (parsed.usageAmount !== undefined) code.usageAmount = parsed.usageAmount ?? null\n if (parsed.usagePerCustomer !== undefined) code.usagePerCustomer = parsed.usagePerCustomer ?? null\n if (parsed.active !== undefined) code.active = parsed.active\n },\n ],\n })\n\n return { codeId: code.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, result.codeId)\n return code ? toCodeSnapshot(code) : null\n },\n buildLog: async ({ result, snapshots, ctx }) => {\n const before = snapshots.before as CodeSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, result.codeId)\n const after = code ? toCodeSnapshot(code) : null\n return {\n actionLabel: translate('promotions.audit.code.update', 'Update promotional code'),\n resourceKind: 'promotions.code',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n payload: {\n undo: { before, after } satisfies CodeUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CodeUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await em.findOne(Code, { id: before.id })\n if (!code) return\n\n await withAtomicFlush(\n em,\n [\n () => {\n code.name = before.name\n code.type = before.type\n code.code = before.code\n code.prefix = before.prefix\n code.suffix = before.suffix\n code.length = before.length\n code.amount = before.amount\n code.usage = before.usage\n code.usageAmount = before.usageAmount\n code.usagePerCustomer = before.usagePerCustomer\n code.active = before.active\n },\n ],\n { transaction: true, label: 'promotions.code.update.undo' },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: code,\n identifiers: getCodeIdentifiers(code),\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n })\n },\n}\n\nconst deleteCodeCommand: CommandHandler<PromotionCodeDeleteInput, { codeId: string }> = {\n id: 'promotions.code.delete',\n outputSchema: codeCommandOutputSchema,\n async prepare(rawInput, ctx) {\n const parsed = promotionCodeDeleteSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, parsed.id)\n if (!code) return null\n // \u00A7API Contracts `DELETE /api/promotions/codes`: \"deactivates generated codes.\"\n // Captured here, before the mutation, so undo restores EXACTLY the rows THIS delete\n // deactivated \u2014 not every generated row for the code, some of which may already be\n // inactive for an unrelated reason (e.g. `codes/use` spending them).\n const activeGeneratedCodeIds = (\n await em.find(GeneratedCode, {\n codeId: code.id,\n tenantId: code.tenantId,\n organizationId: code.organizationId,\n active: true,\n })\n ).map((row) => row.id)\n return { before: { ...toCodeSnapshot(code), activeGeneratedCodeIds } satisfies CodeDeleteSnapshot }\n },\n async execute(rawInput, ctx) {\n const parsed = promotionCodeDeleteSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, parsed.id)\n if (!code) throw notFound('Promotion code not found')\n assertPromotionScopeOrNotFound(ctx, code)\n\n // \u00A7Data Models / \u00A7API Contracts: soft-delete the `Code` row AND explicitly\n // deactivate its already-generated codes. Decision (documented, not left implicit):\n // `resolveCodeByString`'s parent-code lookup already filters `deletedAt: null`, so a\n // soft-deleted code's generated rows are unreachable through the normal resolution\n // path regardless of this flag \u2014 but the explicit flip keeps the admin UI's own\n // generated-codes panel accurate and matches `codes/use`'s identical `active`\n // semantics, rather than relying on that implicit effect alone.\n await withAtomicFlush(\n em,\n [\n () => {\n code.deletedAt = new Date()\n },\n async () => {\n await em.nativeUpdate(\n GeneratedCode,\n { codeId: code.id, tenantId: code.tenantId, organizationId: code.organizationId },\n { active: false },\n )\n },\n ],\n { transaction: true, label: 'promotions.code.delete' },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: code,\n identifiers: getCodeIdentifiers(code),\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n })\n\n return { codeId: code.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as CodeDeleteSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('promotions.audit.code.delete', 'Delete promotional code'),\n resourceKind: 'promotions.code',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: { before } satisfies CodeUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CodeUndoPayload>(logEntry)\n const before = payload?.before as CodeDeleteSnapshot | undefined\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await em.findOne(Code, { id: before.id })\n if (!code) return\n\n await withAtomicFlush(\n em,\n [\n () => {\n code.deletedAt = null\n },\n async () => {\n if (before.activeGeneratedCodeIds.length) {\n await em.nativeUpdate(GeneratedCode, { id: { $in: before.activeGeneratedCodeIds } }, { active: true })\n }\n },\n ],\n { transaction: true, label: 'promotions.code.delete.undo' },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n // Restoring a soft-deleted row re-adds it to the index/cache \u2014 same semantics as\n // `created` on the forward path (mirrors `commands/promotions.ts`'s delete undo).\n action: 'created',\n entity: code,\n identifiers: getCodeIdentifiers(code),\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n })\n },\n}\n\nregisterCommand(reserveCodeCommand)\nregisterCommand(releaseCodeCommand)\nregisterCommand(useCodeCommand)\nregisterCommand(createCodeCommand)\nregisterCommand(updateCodeCommand)\nregisterCommand(deleteCodeCommand)\n\nexport {\n reserveCodeCommand,\n releaseCodeCommand,\n useCodeCommand,\n createCodeCommand,\n updateCodeCommand,\n deleteCodeCommand,\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { runCrudCommandWrite } from '@open-mercato/shared/lib/commands/runCrudCommandWrite'\nimport { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { LockMode } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n Code,\n GeneratedCode,\n type PromotionCodeType,\n type PromotionCodeUsageMode,\n} from '../data/entities'\nimport { assertPromotionScopeOrNotFound } from './shared'\nimport {\n codeReservationReleaseSchema,\n codeReserveInputSchema,\n codeUseInputSchema,\n promotionCodeCreateSchema,\n promotionCodeUpdateSchema,\n promotionCodeDeleteSchema,\n type PromotionCodeCreateInput,\n type PromotionCodeUpdateInput,\n type PromotionCodeDeleteInput,\n} from '../data/validators'\nimport { PromotionCodeService, hasRemainingGlobalUses, type PromotionsCodeScope } from '../lib/code-service'\nimport type { PromotionCodeGenerationService } from '../services/code-generation'\nimport type { CodeReservationResult, CodeUseResult } from '../lib/types'\n\nconst codesLogger = createLogger('promotions').child({ component: 'code-commands' })\n\n// SPEC-055 \u00A7Amendment A.2a: `reserveCode`, `releaseCode` and `useCode` are \"financially\n// or budget consequential writes\" and go through the command pattern the same as\n// `Promotion`/`Code` CRUD. `validateCode`/`extendReservation` are deliberately NOT here\n// \u2014 \u00A7A.2a names exactly five commands for the whole of Phase 3\n// (`reserve`/`release`/`use`/`usage.register`/`usage.revert`; the last two are Step\n// 6.3's), and the spec explicitly calls `validateCode`/`extendReservation` plain\n// reads/housekeeping with no mutation to command-wrap.\n//\n// Each command wraps `lib/code-service.ts`'s `PromotionCodeService` \u2014 the actual\n// resolution/locking/transaction logic lives there (\u00A7Code Lifecycle,\n// \u00A7Promotion Usage Lifecycle's row-lock correction applied to `codes/use`); this file\n// only resolves the command-runtime scope/EntityManager and dispatches to it, same split\n// as `commands/promotions.ts`'s thin command bodies around `data/validators.ts` schemas.\n\n// Mirrors `commands/promotions.ts`'s `resolveActorScope` \u2014 a code operation's\n// tenant/organization scope is always the caller's own actor scope (never a\n// client-supplied value), resolved from `ctx.auth`/`ctx.selectedOrganizationId`.\nfunction resolveActorScope(ctx: CommandRuntimeContext): PromotionsCodeScope {\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, {\n error: '[internal] Tenant and organization scope are required for a promotion code operation',\n })\n }\n ensureTenantScope(ctx, tenantId)\n ensureOrganizationScope(ctx, organizationId)\n return { tenantId, organizationId }\n}\n\n// `ctx.transactionalEm`, when set, is an already-open (possibly ambient \u2014 e.g.\n// `checkout`'s own) transaction this command MUST reuse rather than opening its own\n// (\u00A7Promotion Usage Lifecycle's row-lock correction depends on this for `codes/use`:\n// locking inside the SAME transaction as the caller is what makes the lock effective\n// whether the call is top-level or nested). Otherwise fork the request `em`, same\n// precedent as every other command in this module.\nfunction resolveCommandEm(ctx: CommandRuntimeContext): EntityManager {\n return ctx.transactionalEm ?? (ctx.container.resolve('em') as EntityManager).fork()\n}\n\nfunction resolveCodeService(ctx: CommandRuntimeContext): PromotionCodeService {\n return ctx.container.resolve('promotionCodeService') as PromotionCodeService\n}\n\nconst reserveCodeCommand: CommandHandler<unknown, CodeReservationResult> = {\n id: 'promotions.code.reserve',\n async execute(rawInput, ctx) {\n const parsed = codeReserveInputSchema.parse(rawInput)\n const scope = resolveActorScope(ctx)\n const em = resolveCommandEm(ctx)\n return resolveCodeService(ctx).reserveCode(em, parsed, scope)\n },\n}\n\nconst releaseCodeCommand: CommandHandler<unknown, { ok: true }> = {\n id: 'promotions.code.release',\n async execute(rawInput, ctx) {\n const parsed = codeReservationReleaseSchema.parse(rawInput)\n const scope = resolveActorScope(ctx)\n const em = resolveCommandEm(ctx)\n await resolveCodeService(ctx).releaseCode(em, parsed.codeId, parsed.cartId, scope)\n return { ok: true }\n },\n}\n\nconst useCodeCommand: CommandHandler<unknown, CodeUseResult> = {\n id: 'promotions.code.use',\n async execute(rawInput, ctx) {\n const parsed = codeUseInputSchema.parse(rawInput)\n const scope = resolveActorScope(ctx)\n const em = resolveCommandEm(ctx)\n return resolveCodeService(ctx).useCode(em, parsed, scope)\n },\n}\n\n// ---------------------------------------------------------------------------\n// Code CRUD (\u00A7Implementation Plan Phase 3 Step 3 \"commands/codes.ts \u2014 code CRUD with\n// undo, events\" \u2014 not built by Step 6.2, which only covered the reserve/release/use\n// commands above; Release 4 PLAN.md Step 6.4 closes that gap since `api/codes/route.ts`\n// needs it). Mirrors `commands/promotions.ts`'s create/update/delete shape exactly \u2014\n// command-driven writes, `runCrudCommandWrite`/`withAtomicFlush` for atomicity,\n// `emitCrudSideEffects`/`emitCrudUndoSideEffects` wired to the already-declared\n// `promotions.code.created/updated/deleted` events (`events.ts`) \u2014 so `api/codes/route.ts`\n// can follow `api/route.ts`'s own `makeCrudRoute` template literally.\n// ---------------------------------------------------------------------------\n\nconst CODE_ENTITY_ID = E.promotions.code\n\nconst codeCrudIndexer: CrudIndexerConfig<Code> = {\n entityType: CODE_ENTITY_ID,\n}\n\nconst codeCrudEvents: CrudEventsConfig<Code> = {\n module: 'promotions',\n entity: 'code',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nconst codeCommandOutputSchema = z.object({ codeId: z.string().uuid() })\n\ntype CodeSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n name: string\n type: PromotionCodeType\n code: string | null\n prefix: string | null\n suffix: string | null\n length: number | null\n amount: number | null\n generated: number\n used: number\n usage: PromotionCodeUsageMode\n usageAmount: number | null\n usagePerCustomer: number | null\n active: boolean\n updatedAt: string\n}\n\n// Delete-only extension of `CodeSnapshot` (rev \u2014 the generic `prepare()` return type is\n// `{ before?: unknown }`, so any extra data a specific command's undo needs must live\n// INSIDE the snapshot object itself, not as a sibling key \u2014 see\n// `@open-mercato/shared/lib/commands/types.ts`'s `CommandLogBuilderArgs.snapshots` shape).\ntype CodeDeleteSnapshot = CodeSnapshot & { activeGeneratedCodeIds: string[] }\n\ntype CodeUndoPayload = {\n before?: CodeSnapshot | CodeDeleteSnapshot | null\n after?: CodeSnapshot | null\n}\n\nfunction toCodeSnapshot(code: Code): CodeSnapshot {\n return {\n id: code.id,\n organizationId: code.organizationId,\n tenantId: code.tenantId,\n name: code.name,\n type: code.type,\n code: code.code ?? null,\n prefix: code.prefix ?? null,\n suffix: code.suffix ?? null,\n length: code.length ?? null,\n amount: code.amount ?? null,\n generated: code.generated,\n used: code.used,\n usage: code.usage,\n usageAmount: code.usageAmount ?? null,\n usagePerCustomer: code.usagePerCustomer ?? null,\n active: code.active,\n updatedAt: code.updatedAt.toISOString(),\n }\n}\n\nfunction getCodeIdentifiers(code: Code) {\n return { id: code.id, organizationId: code.organizationId, tenantId: code.tenantId }\n}\n\nasync function loadCode(em: EntityManager, id: string): Promise<Code | null> {\n return em.findOne(Code, { id, deletedAt: null })\n}\n\nfunction resolveUndoActiveFlag(code: Code, before: CodeSnapshot): boolean {\n if (!before.active || code.type !== 'static') return before.active\n const redeemedSinceUpdate = code.used > before.used\n return !redeemedSinceUpdate || hasRemainingGlobalUses(code)\n}\n\n// Mirrors `commands/promotions.ts`'s `resolveActorScope` \u2014 a new code's scope is always\n// the caller's own actor scope, never a client-supplied value.\nfunction resolveCodeActorScope(ctx: CommandRuntimeContext): PromotionsCodeScope {\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, {\n error: '[internal] Tenant and organization scope are required to create a promotion code',\n })\n }\n return { tenantId, organizationId }\n}\n\nconst createCodeCommand: CommandHandler<PromotionCodeCreateInput, { codeId: string }> = {\n id: 'promotions.code.create',\n outputSchema: codeCommandOutputSchema,\n async execute(rawInput, ctx) {\n const parsed = promotionCodeCreateSchema.parse(rawInput)\n const scope = resolveCodeActorScope(ctx)\n ensureTenantScope(ctx, scope.tenantId)\n ensureOrganizationScope(ctx, scope.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = em.create(Code, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n name: parsed.name,\n type: parsed.type,\n code: parsed.code ?? null,\n prefix: parsed.prefix ?? null,\n suffix: parsed.suffix ?? null,\n length: parsed.length ?? null,\n amount: parsed.amount ?? null,\n usage: parsed.usage,\n usageAmount: parsed.usageAmount ?? null,\n usagePerCustomer: parsed.usagePerCustomer ?? null,\n active: parsed.active,\n })\n em.persist(code)\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: code,\n identifiers: getCodeIdentifiers(code),\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n })\n\n // \u00A7API Contracts `POST /api/promotions/codes`: \"for dynamic type, triggers\n // generation worker.\" Fired after the row has committed and its CRUD side effects\n // ran \u2014 a transient enqueue failure must not fail the create itself; the admin can\n // retry generation via `POST /codes/[id]/generate`.\n if (code.type === 'dynamic') {\n try {\n const generationService = ctx.container.resolve('promotionCodeGenerationService') as PromotionCodeGenerationService\n await generationService.triggerGeneration(code.id, scope)\n } catch (err) {\n codesLogger.warn('[internal] promotions.code.create failed to trigger code generation', { codeId: code.id, err })\n }\n }\n\n return { codeId: code.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, result.codeId)\n return code ? toCodeSnapshot(code) : null\n },\n buildLog: async ({ result, snapshots }) => {\n const { translate } = await resolveTranslations()\n const after = snapshots.after as CodeSnapshot | undefined\n return {\n actionLabel: translate('promotions.audit.code.create', 'Create promotional code'),\n resourceKind: 'promotions.code',\n resourceId: result.codeId,\n tenantId: after?.tenantId ?? null,\n organizationId: after?.organizationId ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { after: after ?? null } satisfies CodeUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CodeUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await em.findOne(Code, { id: after.id })\n if (!code) return\n\n // `promotion_generated_codes.code_id` carries `ON DELETE CASCADE` (\u00A7Data Models), so\n // removing the Code row also removes every generated code minted for it.\n em.remove(code)\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'deleted',\n entity: code,\n identifiers: getCodeIdentifiers(code),\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n })\n },\n}\n\nconst updateCodeCommand: CommandHandler<PromotionCodeUpdateInput, { codeId: string }> = {\n id: 'promotions.code.update',\n outputSchema: codeCommandOutputSchema,\n async prepare(rawInput, ctx) {\n const parsed = promotionCodeUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, parsed.id)\n return code ? { before: toCodeSnapshot(code) } : null\n },\n async execute(rawInput, ctx) {\n const parsed = promotionCodeUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, parsed.id)\n if (!code) throw notFound('Promotion code not found')\n assertPromotionScopeOrNotFound(ctx, code)\n\n await runCrudCommandWrite({\n ctx,\n em,\n entityId: CODE_ENTITY_ID,\n action: 'updated',\n scope: { tenantId: code.tenantId, organizationId: code.organizationId },\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n sideEffect: () => ({\n entity: code,\n identifiers: getCodeIdentifiers(code),\n }),\n phases: [\n () => {\n if (parsed.name !== undefined) code.name = parsed.name\n if (parsed.type !== undefined) code.type = parsed.type\n if (parsed.code !== undefined) code.code = parsed.code ?? null\n if (parsed.prefix !== undefined) code.prefix = parsed.prefix ?? null\n if (parsed.suffix !== undefined) code.suffix = parsed.suffix ?? null\n if (parsed.length !== undefined) code.length = parsed.length ?? null\n if (parsed.amount !== undefined) code.amount = parsed.amount ?? null\n if (parsed.usage !== undefined) code.usage = parsed.usage\n if (parsed.usageAmount !== undefined) code.usageAmount = parsed.usageAmount ?? null\n if (parsed.usagePerCustomer !== undefined) code.usagePerCustomer = parsed.usagePerCustomer ?? null\n if (parsed.active !== undefined) code.active = parsed.active\n },\n ],\n })\n\n return { codeId: code.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, result.codeId)\n return code ? toCodeSnapshot(code) : null\n },\n buildLog: async ({ result, snapshots, ctx }) => {\n const before = snapshots.before as CodeSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, result.codeId)\n const after = code ? toCodeSnapshot(code) : null\n return {\n actionLabel: translate('promotions.audit.code.update', 'Update promotional code'),\n resourceKind: 'promotions.code',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n payload: {\n undo: { before, after } satisfies CodeUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CodeUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let code = null as Code | null\n\n await withAtomicFlush(\n em,\n [\n async () => {\n code = await em.findOne(\n Code,\n { id: before.id, tenantId: before.tenantId, organizationId: before.organizationId },\n { lockMode: LockMode.PESSIMISTIC_WRITE },\n )\n },\n () => {\n if (!code) return\n code.name = before.name\n code.type = before.type\n code.code = before.code\n code.prefix = before.prefix\n code.suffix = before.suffix\n code.length = before.length\n code.amount = before.amount\n code.usage = before.usage\n code.usageAmount = before.usageAmount\n code.usagePerCustomer = before.usagePerCustomer\n code.active = resolveUndoActiveFlag(code, before)\n },\n ],\n { transaction: true, label: 'promotions.code.update.undo' },\n )\n if (!code) return\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: code,\n identifiers: getCodeIdentifiers(code),\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n })\n },\n}\n\nconst deleteCodeCommand: CommandHandler<PromotionCodeDeleteInput, { codeId: string }> = {\n id: 'promotions.code.delete',\n outputSchema: codeCommandOutputSchema,\n async prepare(rawInput, ctx) {\n const parsed = promotionCodeDeleteSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, parsed.id)\n if (!code) return null\n // \u00A7API Contracts `DELETE /api/promotions/codes`: \"deactivates generated codes.\"\n // Captured here, before the mutation, so undo restores EXACTLY the rows THIS delete\n // deactivated \u2014 not every generated row for the code, some of which may already be\n // inactive for an unrelated reason (e.g. `codes/use` spending them).\n const activeGeneratedCodeIds = (\n await em.find(GeneratedCode, {\n codeId: code.id,\n tenantId: code.tenantId,\n organizationId: code.organizationId,\n active: true,\n })\n ).map((row) => row.id)\n return { before: { ...toCodeSnapshot(code), activeGeneratedCodeIds } satisfies CodeDeleteSnapshot }\n },\n async execute(rawInput, ctx) {\n const parsed = promotionCodeDeleteSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await loadCode(em, parsed.id)\n if (!code) throw notFound('Promotion code not found')\n assertPromotionScopeOrNotFound(ctx, code)\n\n // \u00A7Data Models / \u00A7API Contracts: soft-delete the `Code` row AND explicitly\n // deactivate its already-generated codes. Decision (documented, not left implicit):\n // `resolveCodeByString`'s parent-code lookup already filters `deletedAt: null`, so a\n // soft-deleted code's generated rows are unreachable through the normal resolution\n // path regardless of this flag \u2014 but the explicit flip keeps the admin UI's own\n // generated-codes panel accurate and matches `codes/use`'s identical `active`\n // semantics, rather than relying on that implicit effect alone.\n await withAtomicFlush(\n em,\n [\n () => {\n code.deletedAt = new Date()\n },\n async () => {\n await em.nativeUpdate(\n GeneratedCode,\n { codeId: code.id, tenantId: code.tenantId, organizationId: code.organizationId },\n { active: false },\n )\n },\n ],\n { transaction: true, label: 'promotions.code.delete' },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: code,\n identifiers: getCodeIdentifiers(code),\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n })\n\n return { codeId: code.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as CodeDeleteSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('promotions.audit.code.delete', 'Delete promotional code'),\n resourceKind: 'promotions.code',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: { before } satisfies CodeUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CodeUndoPayload>(logEntry)\n const before = payload?.before as CodeDeleteSnapshot | undefined\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const code = await em.findOne(Code, { id: before.id })\n if (!code) return\n\n await withAtomicFlush(\n em,\n [\n () => {\n code.deletedAt = null\n },\n async () => {\n if (before.activeGeneratedCodeIds.length) {\n await em.nativeUpdate(GeneratedCode, { id: { $in: before.activeGeneratedCodeIds } }, { active: true })\n }\n },\n ],\n { transaction: true, label: 'promotions.code.delete.undo' },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudUndoSideEffects({\n dataEngine,\n // Restoring a soft-deleted row re-adds it to the index/cache \u2014 same semantics as\n // `created` on the forward path (mirrors `commands/promotions.ts`'s delete undo).\n action: 'created',\n entity: code,\n identifiers: getCodeIdentifiers(code),\n events: codeCrudEvents,\n indexer: codeCrudIndexer,\n })\n },\n}\n\nregisterCommand(reserveCodeCommand)\nregisterCommand(releaseCodeCommand)\nregisterCommand(useCodeCommand)\nregisterCommand(createCodeCommand)\nregisterCommand(updateCodeCommand)\nregisterCommand(deleteCodeCommand)\n\nexport {\n reserveCodeCommand,\n releaseCodeCommand,\n useCodeCommand,\n createCodeCommand,\n updateCodeCommand,\n deleteCodeCommand,\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
createEmptyUpsertIndexBatchResult,
|
|
14
14
|
mergeUpsertIndexBatchResults
|
|
15
15
|
} from "./lib/batch.js";
|
|
16
|
-
import { reindexEntity, DEFAULT_REINDEX_PARTITIONS } from "./lib/reindexer.js";
|
|
16
|
+
import { reindexEntity, isTenantlessReindexTarget, DEFAULT_REINDEX_PARTITIONS } from "./lib/reindexer.js";
|
|
17
17
|
function warnIfReindexRefused(entityType, result) {
|
|
18
18
|
if (!result.refused) return;
|
|
19
19
|
const detail = result.refused === "no-tenant-column" ? "the source table has no tenant_id column and the entity type is not declared tenant-global" : result.refused === "declared-global-but-org-scoped" ? "the entity type is declared tenant-global but its table carries an organization_id column" : "the source table's columns could not be read, so no scope could be proven (usually transient)";
|
|
@@ -755,6 +755,22 @@ const reindex = {
|
|
|
755
755
|
console.log(`[${idx + 1}/${entityIds.length}] Purging existing index rows for ${id}...`);
|
|
756
756
|
await purgeIndexScope(baseEm, { entityType: id, organizationId: orgId, tenantId });
|
|
757
757
|
}
|
|
758
|
+
if (tenantId != null && await isTenantlessReindexTarget(baseEm, id)) {
|
|
759
|
+
console.log(`[${idx + 1}/${entityIds.length}] Skipping ${id}: its table has no tenant_id column, so it is not tenant-scoped`);
|
|
760
|
+
await recordIndexerLog(
|
|
761
|
+
{ em: baseEm },
|
|
762
|
+
{
|
|
763
|
+
source: "query_index",
|
|
764
|
+
handler: "cli:query_index.reindex",
|
|
765
|
+
message: `Reindex skipped for ${id}: not tenant-scoped`,
|
|
766
|
+
entityType: id,
|
|
767
|
+
tenantId,
|
|
768
|
+
organizationId: orgId ?? null,
|
|
769
|
+
details: { skipped: "no-tenant-column" }
|
|
770
|
+
}
|
|
771
|
+
).catch(() => void 0);
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
758
774
|
console.log(
|
|
759
775
|
`[${idx + 1}/${entityIds.length}] Reindexing ${id}${force ? " (forced)" : ""} in ${partitionTargets.length} partition(s)...`
|
|
760
776
|
);
|