@open-mercato/core 0.4.2-canary-c84cff7ed5 → 0.4.2-canary-02d8ce2991

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/modules/auth/backend/auth/profile/page.js.map +1 -1
  2. package/dist/modules/auth/backend/roles/[id]/edit/page.js +4 -1
  3. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  4. package/dist/modules/auth/backend/users/[id]/edit/page.js +4 -1
  5. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  6. package/dist/modules/auth/cli.js +13 -12
  7. package/dist/modules/auth/cli.js.map +2 -2
  8. package/dist/modules/business_rules/api/execute/route.js +7 -1
  9. package/dist/modules/business_rules/api/execute/route.js.map +2 -2
  10. package/dist/modules/business_rules/lib/rule-engine.js +33 -3
  11. package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
  12. package/dist/modules/dashboards/cli.js +12 -4
  13. package/dist/modules/dashboards/cli.js.map +2 -2
  14. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +16 -11
  15. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +3 -3
  16. package/dist/modules/dashboards/services/widgetDataService.js +46 -2
  17. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  18. package/dist/modules/notifications/data/validators.js +5 -1
  19. package/dist/modules/notifications/data/validators.js.map +2 -2
  20. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +2 -1
  21. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
  22. package/dist/modules/notifications/lib/deliveryConfig.js +4 -2
  23. package/dist/modules/notifications/lib/deliveryConfig.js.map +2 -2
  24. package/dist/modules/notifications/lib/deliveryStrategies.js +14 -0
  25. package/dist/modules/notifications/lib/deliveryStrategies.js.map +7 -0
  26. package/dist/modules/notifications/subscribers/deliver-notification.js +33 -7
  27. package/dist/modules/notifications/subscribers/deliver-notification.js.map +2 -2
  28. package/dist/modules/workflows/lib/transition-handler.js +14 -6
  29. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  30. package/package.json +2 -2
  31. package/src/modules/auth/README.md +1 -1
  32. package/src/modules/auth/__tests__/cli-setup-acl.test.ts +1 -1
  33. package/src/modules/auth/backend/auth/profile/page.tsx +2 -2
  34. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +4 -1
  35. package/src/modules/auth/backend/users/[id]/edit/page.tsx +4 -1
  36. package/src/modules/auth/cli.ts +25 -12
  37. package/src/modules/business_rules/api/execute/route.ts +8 -1
  38. package/src/modules/business_rules/lib/__tests__/rule-engine.test.ts +51 -0
  39. package/src/modules/business_rules/lib/rule-engine.ts +57 -3
  40. package/src/modules/dashboards/cli.ts +14 -4
  41. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +22 -11
  42. package/src/modules/dashboards/services/widgetDataService.ts +52 -2
  43. package/src/modules/notifications/__tests__/deliver-notification.test.ts +195 -0
  44. package/src/modules/notifications/__tests__/deliveryStrategies.test.ts +19 -0
  45. package/src/modules/notifications/__tests__/notificationService.test.ts +208 -0
  46. package/src/modules/notifications/data/validators.ts +5 -0
  47. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +2 -0
  48. package/src/modules/notifications/lib/deliveryConfig.ts +8 -0
  49. package/src/modules/notifications/lib/deliveryStrategies.ts +50 -0
  50. package/src/modules/notifications/subscribers/deliver-notification.ts +39 -10
  51. package/src/modules/workflows/lib/transition-handler.ts +18 -6
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/business_rules/api/execute/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { ruleEngineContextSchema } from '../../data/validators'\nimport * as ruleEngine from '../../lib/rule-engine'\n\nconst executeRequestSchema = z.object({\n entityType: z.string().min(1, 'entityType is required'),\n entityId: z.string().optional(),\n eventType: z.string().optional(),\n data: z.any(),\n dryRun: z.boolean().optional().default(false),\n})\n\nconst executeResponseSchema = z.object({\n allowed: z.boolean(),\n executedRules: z.array(z.object({\n ruleId: z.string(),\n ruleName: z.string(),\n conditionResult: z.boolean(),\n executionTime: z.number(),\n error: z.string().optional(),\n })),\n totalExecutionTime: z.number(),\n errors: z.array(z.string()).optional(),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n})\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['business_rules.execute'] },\n}\n\nexport const metadata = routeMetadata\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n let body: any\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n\n const parsed = executeRequestSchema.safeParse(body)\n if (!parsed.success) {\n const errors = parsed.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)\n return NextResponse.json({ error: `Validation failed: ${errors.join(', ')}` }, { status: 400 })\n }\n\n const { entityType, entityId, eventType, data, dryRun } = parsed.data\n\n const context: ruleEngine.RuleEngineContext = {\n entityType,\n entityId,\n eventType,\n data,\n user: {\n id: auth.sub,\n email: auth.email,\n role: (auth.role as string) ?? undefined,\n },\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? '',\n executedBy: auth.sub ?? auth.email ?? null,\n dryRun,\n }\n\n const validation = ruleEngineContextSchema.safeParse(context)\n if (!validation.success) {\n const errors = validation.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)\n return NextResponse.json({ error: `Invalid execution context: ${errors.join(', ')}` }, { status: 400 })\n }\n\n try {\n const result = await ruleEngine.executeRules(em, context)\n\n const response = {\n allowed: result.allowed,\n executedRules: result.executedRules.map(r => ({\n ruleId: r.rule.ruleId,\n ruleName: r.rule.ruleName,\n ruleType: r.rule.ruleType,\n conditionResult: r.conditionResult,\n actionsExecuted: r.actionsExecuted ? {\n success: r.actionsExecuted.success,\n results: r.actionsExecuted.results.map(ar => ({\n type: ar.action.type,\n success: ar.success,\n error: ar.error,\n })),\n } : null,\n executionTime: r.executionTime,\n error: r.error,\n logId: r.logId,\n })),\n totalExecutionTime: result.totalExecutionTime,\n errors: result.errors,\n logIds: result.logIds,\n }\n\n return NextResponse.json(response, { status: 200 })\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n return NextResponse.json(\n { error: `Rule execution failed: ${errorMessage}` },\n { status: 500 }\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Business Rules',\n summary: 'Execute business rules',\n methods: {\n POST: {\n summary: 'Execute rules for given context',\n description: 'Manually executes applicable business rules for the specified entity type, event, and data. Supports dry-run mode to test rules without executing actions.',\n requestBody: {\n contentType: 'application/json',\n schema: executeRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Rules executed successfully',\n schema: executeResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid request payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Execution error', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,+BAA+B;AACxC,YAAY,gBAAgB;AAE5B,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,YAAY,EAAE,OAAO,EAAE,IAAI,GAAG,wBAAwB;AAAA,EACtD,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,MAAM,EAAE,IAAI;AAAA,EACZ,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,KAAK;AAC9C,CAAC;AAED,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,SAAS,EAAE,QAAQ;AAAA,EACnB,eAAe,EAAE,MAAM,EAAE,OAAO;AAAA,IAC9B,QAAQ,EAAE,OAAO;AAAA,IACjB,UAAU,EAAE,OAAO;AAAA,IACnB,iBAAiB,EAAE,QAAQ;AAAA,IAC3B,eAAe,EAAE,OAAO;AAAA,IACxB,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,CAAC,CAAC;AAAA,EACF,oBAAoB,EAAE,OAAO;AAAA,EAC7B,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AACvC,CAAC;AAED,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,MAAM,gBAAgB;AAAA,EACpB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,wBAAwB,EAAE;AACzE;AAEO,MAAM,WAAW;AAExB,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAQ,IAAI;AAEjC,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1E;AAEA,QAAM,SAAS,qBAAqB,UAAU,IAAI;AAClD,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,SAAS,OAAO,MAAM,OAAO,IAAI,OAAK,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE;AAC/E,WAAO,aAAa,KAAK,EAAE,OAAO,sBAAsB,OAAO,KAAK,IAAI,CAAC,GAAG,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAChG;AAEA,QAAM,EAAE,YAAY,UAAU,WAAW,MAAM,OAAO,IAAI,OAAO;AAEjE,QAAM,UAAwC;AAAA,IAC5C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM;AAAA,MACJ,IAAI,KAAK;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,MAAO,KAAK,QAAmB;AAAA,IACjC;AAAA,IACA,UAAU,KAAK,YAAY;AAAA,IAC3B,gBAAgB,KAAK,SAAS;AAAA,IAC9B,YAAY,KAAK,OAAO,KAAK,SAAS;AAAA,IACtC;AAAA,EACF;AAEA,QAAM,aAAa,wBAAwB,UAAU,OAAO;AAC5D,MAAI,CAAC,WAAW,SAAS;AACvB,UAAM,SAAS,WAAW,MAAM,OAAO,IAAI,OAAK,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE;AACnF,WAAO,aAAa,KAAK,EAAE,OAAO,8BAA8B,OAAO,KAAK,IAAI,CAAC,GAAG,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxG;AAEA,MAAI;AACF,UAAM,SAAS,MAAM,WAAW,aAAa,IAAI,OAAO;AAExD,UAAM,WAAW;AAAA,MACf,SAAS,OAAO;AAAA,MAChB,eAAe,OAAO,cAAc,IAAI,QAAM;AAAA,QAC5C,QAAQ,EAAE,KAAK;AAAA,QACf,UAAU,EAAE,KAAK;AAAA,QACjB,UAAU,EAAE,KAAK;AAAA,QACjB,iBAAiB,EAAE;AAAA,QACnB,iBAAiB,EAAE,kBAAkB;AAAA,UACnC,SAAS,EAAE,gBAAgB;AAAA,UAC3B,SAAS,EAAE,gBAAgB,QAAQ,IAAI,SAAO;AAAA,YAC5C,MAAM,GAAG,OAAO;AAAA,YAChB,SAAS,GAAG;AAAA,YACZ,OAAO,GAAG;AAAA,UACZ,EAAE;AAAA,QACJ,IAAI;AAAA,QACJ,eAAe,EAAE;AAAA,QACjB,OAAO,EAAE;AAAA,QACT,OAAO,EAAE;AAAA,MACX,EAAE;AAAA,MACF,oBAAoB,OAAO;AAAA,MAC3B,QAAQ,OAAO;AAAA,MACf,QAAQ,OAAO;AAAA,IACjB;AAEA,WAAO,aAAa,KAAK,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,EACpD,SAAS,OAAO;AACd,UAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,0BAA0B,YAAY,GAAG;AAAA,MAClD,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,oBAAoB;AAAA,QACnF,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,oBAAoB;AAAA,QACxE,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,oBAAoB;AAAA,MAC7E;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EventBus } from '@open-mercato/events'\nimport { ruleEngineContextSchema } from '../../data/validators'\nimport * as ruleEngine from '../../lib/rule-engine'\n\nconst executeRequestSchema = z.object({\n entityType: z.string().min(1, 'entityType is required'),\n entityId: z.string().optional(),\n eventType: z.string().optional(),\n data: z.any(),\n dryRun: z.boolean().optional().default(false),\n})\n\nconst executeResponseSchema = z.object({\n allowed: z.boolean(),\n executedRules: z.array(z.object({\n ruleId: z.string(),\n ruleName: z.string(),\n conditionResult: z.boolean(),\n executionTime: z.number(),\n error: z.string().optional(),\n })),\n totalExecutionTime: z.number(),\n errors: z.array(z.string()).optional(),\n})\n\nconst errorResponseSchema = z.object({\n error: z.string(),\n})\n\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['business_rules.execute'] },\n}\n\nexport const metadata = routeMetadata\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n let eventBus: EventBus | null = null\n try {\n eventBus = container.resolve('eventBus') as EventBus\n } catch {\n eventBus = null\n }\n\n let body: any\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n\n const parsed = executeRequestSchema.safeParse(body)\n if (!parsed.success) {\n const errors = parsed.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)\n return NextResponse.json({ error: `Validation failed: ${errors.join(', ')}` }, { status: 400 })\n }\n\n const { entityType, entityId, eventType, data, dryRun } = parsed.data\n\n const context: ruleEngine.RuleEngineContext = {\n entityType,\n entityId,\n eventType,\n data,\n user: {\n id: auth.sub,\n email: auth.email,\n role: (auth.role as string) ?? undefined,\n },\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? '',\n executedBy: auth.sub ?? auth.email ?? null,\n dryRun,\n }\n\n const validation = ruleEngineContextSchema.safeParse(context)\n if (!validation.success) {\n const errors = validation.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)\n return NextResponse.json({ error: `Invalid execution context: ${errors.join(', ')}` }, { status: 400 })\n }\n\n try {\n const result = await ruleEngine.executeRules(em, context, { eventBus })\n\n const response = {\n allowed: result.allowed,\n executedRules: result.executedRules.map(r => ({\n ruleId: r.rule.ruleId,\n ruleName: r.rule.ruleName,\n ruleType: r.rule.ruleType,\n conditionResult: r.conditionResult,\n actionsExecuted: r.actionsExecuted ? {\n success: r.actionsExecuted.success,\n results: r.actionsExecuted.results.map(ar => ({\n type: ar.action.type,\n success: ar.success,\n error: ar.error,\n })),\n } : null,\n executionTime: r.executionTime,\n error: r.error,\n logId: r.logId,\n })),\n totalExecutionTime: result.totalExecutionTime,\n errors: result.errors,\n logIds: result.logIds,\n }\n\n return NextResponse.json(response, { status: 200 })\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n return NextResponse.json(\n { error: `Rule execution failed: ${errorMessage}` },\n { status: 500 }\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Business Rules',\n summary: 'Execute business rules',\n methods: {\n POST: {\n summary: 'Execute rules for given context',\n description: 'Manually executes applicable business rules for the specified entity type, event, and data. Supports dry-run mode to test rules without executing actions.',\n requestBody: {\n contentType: 'application/json',\n schema: executeRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Rules executed successfully',\n schema: executeResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid request payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Execution error', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -23,7 +23,7 @@ async function withTimeout(promise, timeoutMs, errorMessage) {
23
23
  clearTimeout(timeoutId);
24
24
  }
25
25
  }
26
- async function executeRules(em, context) {
26
+ async function executeRules(em, context, options = {}) {
27
27
  const validation = ruleEngineContextSchema.safeParse(context);
28
28
  if (!validation.success) {
29
29
  const validationErrors = validation.error.issues.map((e) => `${e.path.join(".")}: ${e.message}`);
@@ -59,7 +59,7 @@ async function executeRules(em, context) {
59
59
  const executionPromise = (async () => {
60
60
  for (const rule of rules) {
61
61
  try {
62
- const ruleResult = await executeSingleRule(em, rule, context);
62
+ const ruleResult = await executeSingleRule(em, rule, context, options);
63
63
  executedRules.push(ruleResult);
64
64
  if (ruleResult.logId) {
65
65
  logIds.push(ruleResult.logId);
@@ -74,6 +74,16 @@ async function executeRules(em, context) {
74
74
  errors.push(
75
75
  `Unexpected error in rule execution [ruleId=${rule.ruleId}, type=${rule.ruleType}]: ${errorMessage}`
76
76
  );
77
+ if (!context.dryRun) {
78
+ await emitRuleExecutionFailed(options.eventBus, {
79
+ ruleId: rule.ruleId,
80
+ ruleName: rule.ruleName,
81
+ entityType: context.entityType ?? null,
82
+ errorMessage,
83
+ tenantId: context.tenantId,
84
+ organizationId: context.organizationId ?? null
85
+ });
86
+ }
77
87
  executedRules.push({
78
88
  rule,
79
89
  conditionResult: false,
@@ -115,7 +125,7 @@ Stack: ${stack}` : ""}`
115
125
  };
116
126
  }
117
127
  }
118
- async function executeSingleRule(em, rule, context) {
128
+ async function executeSingleRule(em, rule, context, options = {}) {
119
129
  const startTime = Date.now();
120
130
  try {
121
131
  const executeWithTimeout = async () => {
@@ -140,6 +150,14 @@ async function executeSingleRule(em, rule, context) {
140
150
  executionTime: executionTime2,
141
151
  error: result.error
142
152
  });
153
+ await emitRuleExecutionFailed(options.eventBus, {
154
+ ruleId: rule.ruleId,
155
+ ruleName: rule.ruleName,
156
+ entityType: context.entityType ?? null,
157
+ errorMessage: result.error ?? null,
158
+ tenantId: context.tenantId,
159
+ organizationId: context.organizationId ?? null
160
+ });
143
161
  }
144
162
  return {
145
163
  rule,
@@ -199,6 +217,14 @@ async function executeSingleRule(em, rule, context) {
199
217
  executionTime,
200
218
  error: enhancedError
201
219
  });
220
+ await emitRuleExecutionFailed(options.eventBus, {
221
+ ruleId: rule.ruleId,
222
+ ruleName: rule.ruleName,
223
+ entityType: context.entityType ?? null,
224
+ errorMessage: enhancedError,
225
+ tenantId: context.tenantId,
226
+ organizationId: context.organizationId ?? null
227
+ });
202
228
  }
203
229
  return {
204
230
  rule,
@@ -324,6 +350,10 @@ async function logRuleExecution(em, options) {
324
350
  await em.persistAndFlush(log);
325
351
  return log.id;
326
352
  }
353
+ async function emitRuleExecutionFailed(eventBus, payload) {
354
+ if (!eventBus?.emitEvent) return;
355
+ await eventBus.emitEvent("business_rules.rule.execution_failed", payload).catch(() => void 0);
356
+ }
327
357
  export {
328
358
  executeRules,
329
359
  executeSingleRule,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/business_rules/lib/rule-engine.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport { BusinessRule, RuleExecutionLog, type RuleType } from '../data/entities'\nimport * as ruleEvaluator from './rule-evaluator'\nimport * as actionExecutor from './action-executor'\nimport type { RuleEvaluationContext } from './rule-evaluator'\nimport type { ActionContext, ActionExecutionOutcome } from './action-executor'\nimport { ruleEngineContextSchema, ruleDiscoveryOptionsSchema } from '../data/validators'\n\n/**\n * Constants\n */\nconst DEFAULT_ENTITY_ID = 'unknown'\nconst RULE_TYPE_GUARD = 'GUARD'\nconst EXECUTION_RESULT_ERROR = 'ERROR'\nconst EXECUTION_RESULT_SUCCESS = 'SUCCESS'\nconst EXECUTION_RESULT_FAILURE = 'FAILURE'\n\n/**\n * Execution limits\n */\nconst MAX_RULES_PER_EXECUTION = 100\nconst MAX_SINGLE_RULE_TIMEOUT_MS = 30000 // 30 seconds\nconst MAX_TOTAL_EXECUTION_TIMEOUT_MS = 60000 // 60 seconds\n\n/**\n * Rule execution context\n */\nexport interface RuleEngineContext {\n entityType: string\n entityId?: string\n eventType?: string\n data: any\n user?: {\n id?: string\n email?: string\n role?: string\n [key: string]: any\n }\n tenant?: {\n id?: string\n [key: string]: any\n }\n organization?: {\n id?: string\n [key: string]: any\n }\n tenantId: string\n organizationId: string\n executedBy?: string\n dryRun?: boolean\n [key: string]: any\n}\n\n/**\n * Single rule execution result\n */\nexport interface RuleExecutionResult {\n rule: BusinessRule\n conditionResult: boolean\n actionsExecuted: ActionExecutionOutcome | null\n executionTime: number\n error?: string\n logId?: string // Database log ID (if logged)\n}\n\n/**\n * Overall rule engine result\n */\nexport interface RuleEngineResult {\n allowed: boolean\n executedRules: RuleExecutionResult[]\n totalExecutionTime: number\n errors?: string[]\n logIds?: string[]\n}\n\n/**\n * Rule discovery options\n */\nexport interface RuleDiscoveryOptions {\n entityType: string\n eventType?: string\n tenantId: string\n organizationId: string\n ruleType?: RuleType\n}\n\n/**\n * Execute a function with a timeout\n */\nasync function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n errorMessage: string\n): Promise<T> {\n let timeoutId: NodeJS.Timeout\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(`${errorMessage} (timeout: ${timeoutMs}ms)`))\n }, timeoutMs)\n })\n\n try {\n return await Promise.race([promise, timeoutPromise])\n } finally {\n clearTimeout(timeoutId!)\n }\n}\n\n/**\n * Execute all applicable rules for the given context\n */\nexport async function executeRules(\n em: EntityManager,\n context: RuleEngineContext\n): Promise<RuleEngineResult> {\n // Validate input\n const validation = ruleEngineContextSchema.safeParse(context)\n if (!validation.success) {\n const validationErrors = validation.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)\n return {\n allowed: false,\n executedRules: [],\n totalExecutionTime: 0,\n errors: validationErrors,\n }\n }\n\n const startTime = Date.now()\n const executedRules: RuleExecutionResult[] = []\n const errors: string[] = []\n const logIds: string[] = []\n\n try {\n // Discover applicable rules\n const rules = await findApplicableRules(em, {\n entityType: context.entityType,\n eventType: context.eventType,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n\n // Check rule count limit\n if (rules.length > MAX_RULES_PER_EXECUTION) {\n errors.push(\n `Rule count limit exceeded: ${rules.length} rules found, maximum is ${MAX_RULES_PER_EXECUTION}`\n )\n return {\n allowed: false,\n executedRules: [],\n totalExecutionTime: Date.now() - startTime,\n errors,\n }\n }\n\n // Rules already sorted by database query (priority DESC, ruleId ASC)\n // Execute each rule with total timeout\n const executionPromise = (async () => {\n for (const rule of rules) {\n try {\n const ruleResult = await executeSingleRule(em, rule, context)\n executedRules.push(ruleResult)\n\n if (ruleResult.logId) {\n logIds.push(ruleResult.logId)\n }\n\n if (ruleResult.error) {\n errors.push(\n `Rule execution failed [ruleId=${rule.ruleId}, type=${rule.ruleType}, entityType=${context.entityType}]: ${ruleResult.error}`\n )\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n errors.push(\n `Unexpected error in rule execution [ruleId=${rule.ruleId}, type=${rule.ruleType}]: ${errorMessage}`\n )\n\n executedRules.push({\n rule,\n conditionResult: false,\n actionsExecuted: null,\n executionTime: 0,\n error: errorMessage,\n })\n }\n }\n })()\n\n // Execute with timeout\n await withTimeout(\n executionPromise,\n MAX_TOTAL_EXECUTION_TIMEOUT_MS,\n `Total rule execution timeout [entityType=${context.entityType}]`\n )\n\n // Determine overall allowed status\n // For GUARD rules: all must pass for operation to be allowed\n const guardRules = executedRules.filter((r) => r.rule.ruleType === RULE_TYPE_GUARD)\n const allowed = guardRules.length === 0 || guardRules.every((r) => r.conditionResult)\n\n const totalExecutionTime = Date.now() - startTime\n\n return {\n allowed,\n executedRules,\n totalExecutionTime,\n errors: errors.length > 0 ? errors : undefined,\n logIds: logIds.length > 0 ? logIds : undefined,\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n const stack = error instanceof Error ? error.stack : undefined\n errors.push(\n `Critical rule engine error [entityType=${context.entityType}, entityId=${context.entityId || 'unknown'}]: ${errorMessage}${stack ? `\\nStack: ${stack}` : ''}`\n )\n\n const totalExecutionTime = Date.now() - startTime\n\n return {\n allowed: false,\n executedRules,\n totalExecutionTime,\n errors,\n }\n }\n}\n\n/**\n * Execute a single rule\n */\nexport async function executeSingleRule(\n em: EntityManager,\n rule: BusinessRule,\n context: RuleEngineContext\n): Promise<RuleExecutionResult> {\n const startTime = Date.now()\n\n try {\n // Wrap execution in timeout\n const executeWithTimeout = async () => {\n // Build evaluation context\n const evalContext: RuleEvaluationContext = {\n entityType: context.entityType,\n entityId: context.entityId,\n eventType: context.eventType,\n user: context.user,\n tenant: context.tenant,\n organization: context.organization,\n }\n\n // Evaluate rule conditions\n const result = await ruleEvaluator.evaluateSingleRule(rule, context.data, evalContext)\n\n // Check if evaluation completed (not just if conditions passed)\n if (!result.evaluationCompleted) {\n const executionTime = Date.now() - startTime\n\n let logId: string | undefined\n\n // Log failure if not dry run\n if (!context.dryRun) {\n logId = await logRuleExecution(em, {\n rule,\n context,\n conditionResult: false,\n actionsExecuted: null,\n executionTime,\n error: result.error,\n })\n }\n\n return {\n rule,\n conditionResult: false,\n actionsExecuted: null,\n executionTime,\n error: result.error,\n logId,\n }\n }\n\n // Evaluation completed successfully - determine which actions to execute\n const actions = result.conditionsPassed ? rule.successActions : rule.failureActions\n\n let actionsExecuted: ActionExecutionOutcome | null = null\n\n if (actions && Array.isArray(actions) && actions.length > 0) {\n // Build action context\n const actionContext: ActionContext = {\n ...evalContext,\n data: context.data,\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n }\n\n // Execute actions\n actionsExecuted = await actionExecutor.executeActions(actions, actionContext)\n }\n\n const executionTime = Date.now() - startTime\n\n let logId: string | undefined\n\n // Log execution if not dry run\n if (!context.dryRun) {\n logId = await logRuleExecution(em, {\n rule,\n context,\n conditionResult: result.conditionsPassed,\n actionsExecuted,\n executionTime,\n })\n }\n\n return {\n rule,\n conditionResult: result.conditionsPassed,\n actionsExecuted,\n executionTime,\n logId,\n }\n }\n\n // Execute with single rule timeout\n return await withTimeout(\n executeWithTimeout(),\n MAX_SINGLE_RULE_TIMEOUT_MS,\n `Single rule execution timeout [ruleId=${rule.ruleId}]`\n )\n } catch (error) {\n const executionTime = Date.now() - startTime\n const errorMessage = error instanceof Error ? error.message : String(error)\n const enhancedError = `Failed to execute rule [ruleId=${rule.ruleId}, entityType=${context.entityType}]: ${errorMessage}`\n\n let logId: string | undefined\n\n // Log error if not dry run\n if (!context.dryRun) {\n logId = await logRuleExecution(em, {\n rule,\n context,\n conditionResult: false,\n actionsExecuted: null,\n executionTime,\n error: enhancedError,\n })\n }\n\n return {\n rule,\n conditionResult: false,\n actionsExecuted: null,\n executionTime,\n error: enhancedError,\n logId,\n }\n }\n}\n\n/**\n * Find all applicable rules for the given criteria\n */\nexport async function findApplicableRules(\n em: EntityManager,\n options: RuleDiscoveryOptions\n): Promise<BusinessRule[]> {\n // Validate input\n ruleDiscoveryOptionsSchema.parse(options)\n\n const { entityType, eventType, tenantId, organizationId, ruleType } = options\n\n const where: Partial<BusinessRule> = {\n entityType,\n tenantId,\n organizationId,\n enabled: true,\n deletedAt: null,\n }\n\n if (eventType) {\n where.eventType = eventType\n }\n\n if (ruleType) {\n where.ruleType = ruleType\n }\n\n const rules = await em.find(BusinessRule, where, {\n orderBy: { priority: 'DESC', ruleId: 'ASC' },\n })\n\n // Filter by effective date range\n const now = new Date()\n return rules.filter((rule) => {\n if (rule.effectiveFrom && rule.effectiveFrom > now) {\n return false\n }\n if (rule.effectiveTo && rule.effectiveTo < now) {\n return false\n }\n return true\n })\n}\n\n/**\n * Sensitive field patterns to exclude from logs\n */\nconst SENSITIVE_FIELD_PATTERNS = [\n /password/i,\n /passwd/i,\n /pwd/i,\n /secret/i,\n /token/i,\n /api[_-]?key/i,\n /auth/i,\n /credit[_-]?card/i,\n /card[_-]?number/i,\n /cvv/i,\n /ssn/i,\n /social[_-]?security/i,\n /tax[_-]?id/i,\n /driver[_-]?license/i,\n /passport/i,\n]\n\n/**\n * Maximum depth for nested object sanitization\n */\nconst MAX_SANITIZATION_DEPTH = 5\n\n/**\n * Sanitize data for logging by removing sensitive fields\n */\nfunction sanitizeForLogging(data: any, depth: number = 0): any {\n // Prevent infinite recursion\n if (depth > MAX_SANITIZATION_DEPTH) {\n return '[Max depth exceeded]'\n }\n\n // Handle null/undefined\n if (data === null || data === undefined) {\n return data\n }\n\n // Handle primitives\n if (typeof data !== 'object') {\n return data\n }\n\n // Handle arrays\n if (Array.isArray(data)) {\n return data.map(item => sanitizeForLogging(item, depth + 1))\n }\n\n // Handle objects\n const sanitized: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(data)) {\n // Check if field name matches sensitive patterns\n const isSensitive = SENSITIVE_FIELD_PATTERNS.some(pattern => pattern.test(key))\n\n if (isSensitive) {\n sanitized[key] = '[REDACTED]'\n } else if (typeof value === 'object' && value !== null) {\n sanitized[key] = sanitizeForLogging(value, depth + 1)\n } else {\n sanitized[key] = value\n }\n }\n\n return sanitized\n}\n\n/**\n * Sanitize user object for logging (keep only safe fields)\n */\nfunction sanitizeUser(user: any): any {\n if (!user) {\n return undefined\n }\n\n // Only log safe user fields\n return {\n id: user.id,\n role: user.role,\n // Don't log: email, name, phone, address, etc.\n }\n}\n\n/**\n * Log rule execution to database\n */\ninterface LogExecutionOptions {\n rule: BusinessRule\n context: RuleEngineContext\n conditionResult: boolean\n actionsExecuted: ActionExecutionOutcome | null\n executionTime: number\n error?: string\n}\n\nexport async function logRuleExecution(\n em: EntityManager,\n options: LogExecutionOptions\n): Promise<string> {\n const { rule, context, conditionResult, actionsExecuted, executionTime, error } = options\n\n const executionResult: 'SUCCESS' | 'FAILURE' | 'ERROR' = error\n ? EXECUTION_RESULT_ERROR\n : conditionResult\n ? EXECUTION_RESULT_SUCCESS\n : EXECUTION_RESULT_FAILURE\n\n const log = em.create(RuleExecutionLog, {\n rule,\n entityId: context.entityId || DEFAULT_ENTITY_ID,\n entityType: context.entityType,\n executionResult,\n inputContext: {\n data: sanitizeForLogging(context.data),\n eventType: context.eventType,\n user: sanitizeUser(context.user),\n },\n outputContext: actionsExecuted\n ? {\n conditionResult,\n actionsExecuted: actionsExecuted.results.map((r) => ({\n type: r.action.type,\n success: r.success,\n error: r.error,\n })),\n }\n : null,\n errorMessage: error || null,\n executionTimeMs: executionTime,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n executedBy: context.executedBy || null,\n })\n\n await em.persistAndFlush(log)\n\n return log.id\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport type { EventBus } from '@open-mercato/events'\nimport { BusinessRule, RuleExecutionLog, type RuleType } from '../data/entities'\nimport * as ruleEvaluator from './rule-evaluator'\nimport * as actionExecutor from './action-executor'\nimport type { RuleEvaluationContext } from './rule-evaluator'\nimport type { ActionContext, ActionExecutionOutcome } from './action-executor'\nimport { ruleEngineContextSchema, ruleDiscoveryOptionsSchema } from '../data/validators'\n\n/**\n * Constants\n */\nconst DEFAULT_ENTITY_ID = 'unknown'\nconst RULE_TYPE_GUARD = 'GUARD'\nconst EXECUTION_RESULT_ERROR = 'ERROR'\nconst EXECUTION_RESULT_SUCCESS = 'SUCCESS'\nconst EXECUTION_RESULT_FAILURE = 'FAILURE'\n\n/**\n * Execution limits\n */\nconst MAX_RULES_PER_EXECUTION = 100\nconst MAX_SINGLE_RULE_TIMEOUT_MS = 30000 // 30 seconds\nconst MAX_TOTAL_EXECUTION_TIMEOUT_MS = 60000 // 60 seconds\n\n/**\n * Rule execution context\n */\nexport interface RuleEngineContext {\n entityType: string\n entityId?: string\n eventType?: string\n data: any\n user?: {\n id?: string\n email?: string\n role?: string\n [key: string]: any\n }\n tenant?: {\n id?: string\n [key: string]: any\n }\n organization?: {\n id?: string\n [key: string]: any\n }\n tenantId: string\n organizationId: string\n executedBy?: string\n dryRun?: boolean\n [key: string]: any\n}\n\n/**\n * Single rule execution result\n */\nexport interface RuleExecutionResult {\n rule: BusinessRule\n conditionResult: boolean\n actionsExecuted: ActionExecutionOutcome | null\n executionTime: number\n error?: string\n logId?: string // Database log ID (if logged)\n}\n\n/**\n * Overall rule engine result\n */\nexport interface RuleEngineResult {\n allowed: boolean\n executedRules: RuleExecutionResult[]\n totalExecutionTime: number\n errors?: string[]\n logIds?: string[]\n}\n\n/**\n * Rule discovery options\n */\nexport interface RuleDiscoveryOptions {\n entityType: string\n eventType?: string\n tenantId: string\n organizationId: string\n ruleType?: RuleType\n}\n\nexport type RuleEngineExecutionOptions = {\n eventBus?: Pick<EventBus, 'emitEvent'> | null\n}\n\ntype RuleExecutionFailedPayload = {\n ruleId: string\n ruleName: string\n entityType?: string | null\n errorMessage?: string | null\n tenantId: string\n organizationId?: string | null\n}\n\n/**\n * Execute a function with a timeout\n */\nasync function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n errorMessage: string\n): Promise<T> {\n let timeoutId: NodeJS.Timeout\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(`${errorMessage} (timeout: ${timeoutMs}ms)`))\n }, timeoutMs)\n })\n\n try {\n return await Promise.race([promise, timeoutPromise])\n } finally {\n clearTimeout(timeoutId!)\n }\n}\n\n/**\n * Execute all applicable rules for the given context\n */\nexport async function executeRules(\n em: EntityManager,\n context: RuleEngineContext,\n options: RuleEngineExecutionOptions = {}\n): Promise<RuleEngineResult> {\n // Validate input\n const validation = ruleEngineContextSchema.safeParse(context)\n if (!validation.success) {\n const validationErrors = validation.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)\n return {\n allowed: false,\n executedRules: [],\n totalExecutionTime: 0,\n errors: validationErrors,\n }\n }\n\n const startTime = Date.now()\n const executedRules: RuleExecutionResult[] = []\n const errors: string[] = []\n const logIds: string[] = []\n\n try {\n // Discover applicable rules\n const rules = await findApplicableRules(em, {\n entityType: context.entityType,\n eventType: context.eventType,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n\n // Check rule count limit\n if (rules.length > MAX_RULES_PER_EXECUTION) {\n errors.push(\n `Rule count limit exceeded: ${rules.length} rules found, maximum is ${MAX_RULES_PER_EXECUTION}`\n )\n return {\n allowed: false,\n executedRules: [],\n totalExecutionTime: Date.now() - startTime,\n errors,\n }\n }\n\n // Rules already sorted by database query (priority DESC, ruleId ASC)\n // Execute each rule with total timeout\n const executionPromise = (async () => {\n for (const rule of rules) {\n try {\n const ruleResult = await executeSingleRule(em, rule, context, options)\n executedRules.push(ruleResult)\n\n if (ruleResult.logId) {\n logIds.push(ruleResult.logId)\n }\n\n if (ruleResult.error) {\n errors.push(\n `Rule execution failed [ruleId=${rule.ruleId}, type=${rule.ruleType}, entityType=${context.entityType}]: ${ruleResult.error}`\n )\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n errors.push(\n `Unexpected error in rule execution [ruleId=${rule.ruleId}, type=${rule.ruleType}]: ${errorMessage}`\n )\n\n if (!context.dryRun) {\n await emitRuleExecutionFailed(options.eventBus, {\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n entityType: context.entityType ?? null,\n errorMessage,\n tenantId: context.tenantId,\n organizationId: context.organizationId ?? null,\n })\n }\n\n executedRules.push({\n rule,\n conditionResult: false,\n actionsExecuted: null,\n executionTime: 0,\n error: errorMessage,\n })\n }\n }\n })()\n\n // Execute with timeout\n await withTimeout(\n executionPromise,\n MAX_TOTAL_EXECUTION_TIMEOUT_MS,\n `Total rule execution timeout [entityType=${context.entityType}]`\n )\n\n // Determine overall allowed status\n // For GUARD rules: all must pass for operation to be allowed\n const guardRules = executedRules.filter((r) => r.rule.ruleType === RULE_TYPE_GUARD)\n const allowed = guardRules.length === 0 || guardRules.every((r) => r.conditionResult)\n\n const totalExecutionTime = Date.now() - startTime\n\n return {\n allowed,\n executedRules,\n totalExecutionTime,\n errors: errors.length > 0 ? errors : undefined,\n logIds: logIds.length > 0 ? logIds : undefined,\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n const stack = error instanceof Error ? error.stack : undefined\n errors.push(\n `Critical rule engine error [entityType=${context.entityType}, entityId=${context.entityId || 'unknown'}]: ${errorMessage}${stack ? `\\nStack: ${stack}` : ''}`\n )\n\n const totalExecutionTime = Date.now() - startTime\n\n return {\n allowed: false,\n executedRules,\n totalExecutionTime,\n errors,\n }\n }\n}\n\n/**\n * Execute a single rule\n */\nexport async function executeSingleRule(\n em: EntityManager,\n rule: BusinessRule,\n context: RuleEngineContext,\n options: RuleEngineExecutionOptions = {}\n): Promise<RuleExecutionResult> {\n const startTime = Date.now()\n\n try {\n // Wrap execution in timeout\n const executeWithTimeout = async () => {\n // Build evaluation context\n const evalContext: RuleEvaluationContext = {\n entityType: context.entityType,\n entityId: context.entityId,\n eventType: context.eventType,\n user: context.user,\n tenant: context.tenant,\n organization: context.organization,\n }\n\n // Evaluate rule conditions\n const result = await ruleEvaluator.evaluateSingleRule(rule, context.data, evalContext)\n\n // Check if evaluation completed (not just if conditions passed)\n if (!result.evaluationCompleted) {\n const executionTime = Date.now() - startTime\n\n let logId: string | undefined\n\n // Log failure if not dry run\n if (!context.dryRun) {\n logId = await logRuleExecution(em, {\n rule,\n context,\n conditionResult: false,\n actionsExecuted: null,\n executionTime,\n error: result.error,\n })\n\n await emitRuleExecutionFailed(options.eventBus, {\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n entityType: context.entityType ?? null,\n errorMessage: result.error ?? null,\n tenantId: context.tenantId,\n organizationId: context.organizationId ?? null,\n })\n }\n\n return {\n rule,\n conditionResult: false,\n actionsExecuted: null,\n executionTime,\n error: result.error,\n logId,\n }\n }\n\n // Evaluation completed successfully - determine which actions to execute\n const actions = result.conditionsPassed ? rule.successActions : rule.failureActions\n\n let actionsExecuted: ActionExecutionOutcome | null = null\n\n if (actions && Array.isArray(actions) && actions.length > 0) {\n // Build action context\n const actionContext: ActionContext = {\n ...evalContext,\n data: context.data,\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n }\n\n // Execute actions\n actionsExecuted = await actionExecutor.executeActions(actions, actionContext)\n }\n\n const executionTime = Date.now() - startTime\n\n let logId: string | undefined\n\n // Log execution if not dry run\n if (!context.dryRun) {\n logId = await logRuleExecution(em, {\n rule,\n context,\n conditionResult: result.conditionsPassed,\n actionsExecuted,\n executionTime,\n })\n }\n\n return {\n rule,\n conditionResult: result.conditionsPassed,\n actionsExecuted,\n executionTime,\n logId,\n }\n }\n\n // Execute with single rule timeout\n return await withTimeout(\n executeWithTimeout(),\n MAX_SINGLE_RULE_TIMEOUT_MS,\n `Single rule execution timeout [ruleId=${rule.ruleId}]`\n )\n } catch (error) {\n const executionTime = Date.now() - startTime\n const errorMessage = error instanceof Error ? error.message : String(error)\n const enhancedError = `Failed to execute rule [ruleId=${rule.ruleId}, entityType=${context.entityType}]: ${errorMessage}`\n\n let logId: string | undefined\n\n // Log error if not dry run\n if (!context.dryRun) {\n logId = await logRuleExecution(em, {\n rule,\n context,\n conditionResult: false,\n actionsExecuted: null,\n executionTime,\n error: enhancedError,\n })\n\n await emitRuleExecutionFailed(options.eventBus, {\n ruleId: rule.ruleId,\n ruleName: rule.ruleName,\n entityType: context.entityType ?? null,\n errorMessage: enhancedError,\n tenantId: context.tenantId,\n organizationId: context.organizationId ?? null,\n })\n }\n\n return {\n rule,\n conditionResult: false,\n actionsExecuted: null,\n executionTime,\n error: enhancedError,\n logId,\n }\n }\n}\n\n/**\n * Find all applicable rules for the given criteria\n */\nexport async function findApplicableRules(\n em: EntityManager,\n options: RuleDiscoveryOptions\n): Promise<BusinessRule[]> {\n // Validate input\n ruleDiscoveryOptionsSchema.parse(options)\n\n const { entityType, eventType, tenantId, organizationId, ruleType } = options\n\n const where: Partial<BusinessRule> = {\n entityType,\n tenantId,\n organizationId,\n enabled: true,\n deletedAt: null,\n }\n\n if (eventType) {\n where.eventType = eventType\n }\n\n if (ruleType) {\n where.ruleType = ruleType\n }\n\n const rules = await em.find(BusinessRule, where, {\n orderBy: { priority: 'DESC', ruleId: 'ASC' },\n })\n\n // Filter by effective date range\n const now = new Date()\n return rules.filter((rule) => {\n if (rule.effectiveFrom && rule.effectiveFrom > now) {\n return false\n }\n if (rule.effectiveTo && rule.effectiveTo < now) {\n return false\n }\n return true\n })\n}\n\n/**\n * Sensitive field patterns to exclude from logs\n */\nconst SENSITIVE_FIELD_PATTERNS = [\n /password/i,\n /passwd/i,\n /pwd/i,\n /secret/i,\n /token/i,\n /api[_-]?key/i,\n /auth/i,\n /credit[_-]?card/i,\n /card[_-]?number/i,\n /cvv/i,\n /ssn/i,\n /social[_-]?security/i,\n /tax[_-]?id/i,\n /driver[_-]?license/i,\n /passport/i,\n]\n\n/**\n * Maximum depth for nested object sanitization\n */\nconst MAX_SANITIZATION_DEPTH = 5\n\n/**\n * Sanitize data for logging by removing sensitive fields\n */\nfunction sanitizeForLogging(data: any, depth: number = 0): any {\n // Prevent infinite recursion\n if (depth > MAX_SANITIZATION_DEPTH) {\n return '[Max depth exceeded]'\n }\n\n // Handle null/undefined\n if (data === null || data === undefined) {\n return data\n }\n\n // Handle primitives\n if (typeof data !== 'object') {\n return data\n }\n\n // Handle arrays\n if (Array.isArray(data)) {\n return data.map(item => sanitizeForLogging(item, depth + 1))\n }\n\n // Handle objects\n const sanitized: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(data)) {\n // Check if field name matches sensitive patterns\n const isSensitive = SENSITIVE_FIELD_PATTERNS.some(pattern => pattern.test(key))\n\n if (isSensitive) {\n sanitized[key] = '[REDACTED]'\n } else if (typeof value === 'object' && value !== null) {\n sanitized[key] = sanitizeForLogging(value, depth + 1)\n } else {\n sanitized[key] = value\n }\n }\n\n return sanitized\n}\n\n/**\n * Sanitize user object for logging (keep only safe fields)\n */\nfunction sanitizeUser(user: any): any {\n if (!user) {\n return undefined\n }\n\n // Only log safe user fields\n return {\n id: user.id,\n role: user.role,\n // Don't log: email, name, phone, address, etc.\n }\n}\n\n/**\n * Log rule execution to database\n */\ninterface LogExecutionOptions {\n rule: BusinessRule\n context: RuleEngineContext\n conditionResult: boolean\n actionsExecuted: ActionExecutionOutcome | null\n executionTime: number\n error?: string\n}\n\nexport async function logRuleExecution(\n em: EntityManager,\n options: LogExecutionOptions\n): Promise<string> {\n const { rule, context, conditionResult, actionsExecuted, executionTime, error } = options\n\n const executionResult: 'SUCCESS' | 'FAILURE' | 'ERROR' = error\n ? EXECUTION_RESULT_ERROR\n : conditionResult\n ? EXECUTION_RESULT_SUCCESS\n : EXECUTION_RESULT_FAILURE\n\n const log = em.create(RuleExecutionLog, {\n rule,\n entityId: context.entityId || DEFAULT_ENTITY_ID,\n entityType: context.entityType,\n executionResult,\n inputContext: {\n data: sanitizeForLogging(context.data),\n eventType: context.eventType,\n user: sanitizeUser(context.user),\n },\n outputContext: actionsExecuted\n ? {\n conditionResult,\n actionsExecuted: actionsExecuted.results.map((r) => ({\n type: r.action.type,\n success: r.success,\n error: r.error,\n })),\n }\n : null,\n errorMessage: error || null,\n executionTimeMs: executionTime,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n executedBy: context.executedBy || null,\n })\n\n await em.persistAndFlush(log)\n\n return log.id\n}\n\nasync function emitRuleExecutionFailed(\n eventBus: Pick<EventBus, 'emitEvent'> | null | undefined,\n payload: RuleExecutionFailedPayload\n): Promise<void> {\n if (!eventBus?.emitEvent) return\n\n await eventBus.emitEvent('business_rules.rule.execution_failed', payload).catch(() => undefined)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["executionTime", "logId"]
7
7
  }
@@ -25,13 +25,21 @@ async function seedDashboardDefaultsForTenant(em, {
25
25
  });
26
26
  const widgets = await loadAllWidgets();
27
27
  const widgetMap = new Map(widgets.map((widget) => [widget.metadata.id, widget]));
28
- const resolvedWidgetIds = widgetIds && widgetIds.length ? widgetIds.filter((id) => widgetMap.has(id)) : widgets.filter((widget) => widget.metadata.defaultEnabled).map((widget) => widget.metadata.id);
29
- if (!resolvedWidgetIds.length) {
28
+ const resolvedWidgetIds = widgetIds && widgetIds.length ? widgetIds.filter((id) => widgetMap.has(id)) : null;
29
+ const defaultWidgetIds = widgets.filter((widget) => widget.metadata.defaultEnabled).map((widget) => widget.metadata.id);
30
+ const allWidgetIds = widgets.map((widget) => widget.metadata.id);
31
+ if (resolvedWidgetIds && resolvedWidgetIds.length === 0) {
30
32
  log("No widgets resolved for dashboard seeding.");
31
33
  return false;
32
34
  }
33
35
  await em.transactional(async (tem) => {
34
36
  for (const roleName of roleNames) {
37
+ const isAdminRole = roleName === "admin" || roleName === "superadmin";
38
+ const roleWidgetIds = resolvedWidgetIds ?? (isAdminRole ? allWidgetIds : defaultWidgetIds);
39
+ if (!roleWidgetIds.length) {
40
+ log(`No widgets resolved for role "${roleName}".`);
41
+ continue;
42
+ }
35
43
  const role = await tem.findOne(Role, { name: roleName });
36
44
  if (!role) {
37
45
  log(`Skipping role "${roleName}" (not found)`);
@@ -44,7 +52,7 @@ async function seedDashboardDefaultsForTenant(em, {
44
52
  deletedAt: null
45
53
  });
46
54
  if (existing) {
47
- existing.widgetIdsJson = resolvedWidgetIds;
55
+ existing.widgetIdsJson = roleWidgetIds;
48
56
  tem.persist(existing);
49
57
  log(`Updated dashboard widgets for role "${roleName}"`);
50
58
  } else {
@@ -52,7 +60,7 @@ async function seedDashboardDefaultsForTenant(em, {
52
60
  roleId: String(role.id),
53
61
  tenantId,
54
62
  organizationId,
55
- widgetIdsJson: resolvedWidgetIds,
63
+ widgetIdsJson: roleWidgetIds,
56
64
  createdAt: /* @__PURE__ */ new Date(),
57
65
  updatedAt: null,
58
66
  deletedAt: null
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/dashboards/cli.ts"],
4
- "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { DashboardRoleWidgets } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'\nimport { appendWidgetsToRoles, resolveAnalyticsWidgetIds } from '@open-mercato/core/modules/dashboards/lib/role-widgets'\nimport { seedAnalyticsData } from './seed/analytics'\n\ntype Args = Record<string, string>\n\nfunction parseArgs(rest: string[]): Args {\n const args: Args = {}\n for (let i = 0; i < rest.length; i += 2) {\n const key = rest[i]?.replace(/^--/, '')\n const value = rest[i + 1]\n if (key) args[key] = value ?? ''\n }\n return args\n}\n\nexport async function seedDashboardDefaultsForTenant(\n em: EntityManager,\n {\n tenantId,\n organizationId = null,\n roleNames = ['superadmin', 'admin', 'employee'],\n widgetIds,\n logger,\n }: {\n tenantId: string\n organizationId?: string | null\n roleNames?: string[]\n widgetIds?: string[]\n logger?: (message: string) => void\n },\n): Promise<boolean> {\n if (!tenantId) throw new Error('tenantId is required')\n const log = logger ?? (() => {})\n\n const widgets = await loadAllWidgets()\n const widgetMap = new Map(widgets.map((widget) => [widget.metadata.id, widget]))\n const resolvedWidgetIds = widgetIds && widgetIds.length\n ? widgetIds.filter((id) => widgetMap.has(id))\n : widgets.filter((widget) => widget.metadata.defaultEnabled).map((widget) => widget.metadata.id)\n\n if (!resolvedWidgetIds.length) {\n log('No widgets resolved for dashboard seeding.')\n return false\n }\n\n await em.transactional(async (tem) => {\n for (const roleName of roleNames) {\n const role = await tem.findOne(Role, { name: roleName })\n if (!role) {\n log(`Skipping role \"${roleName}\" (not found)`)\n continue\n }\n const existing = await tem.findOne(DashboardRoleWidgets, {\n roleId: String(role.id),\n tenantId,\n organizationId,\n deletedAt: null,\n })\n if (existing) {\n existing.widgetIdsJson = resolvedWidgetIds\n tem.persist(existing)\n log(`Updated dashboard widgets for role \"${roleName}\"`)\n } else {\n const record = tem.create(DashboardRoleWidgets, {\n roleId: String(role.id),\n tenantId,\n organizationId,\n widgetIdsJson: resolvedWidgetIds,\n createdAt: new Date(),\n updatedAt: null,\n deletedAt: null,\n })\n tem.persist(record)\n log(`Created dashboard widgets for role \"${roleName}\"`)\n }\n }\n })\n\n return true\n}\n\nconst seedDefaults: ModuleCli = {\n command: 'seed-defaults',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = args.tenant || args.tenantId || null\n const organizationId = args.organization || args.organizationId || null\n const roleCsv = args.roles || 'superadmin,admin,employee'\n const widgetCsv = args.widgets || ''\n if (!tenantId) {\n console.error('Usage: mercato dashboards seed-defaults --tenant <tenantId> [--roles superadmin,admin,employee] [--widgets id1,id2]')\n return\n }\n\n const roleNames = roleCsv\n .split(',')\n .map((name) => name.trim())\n .filter(Boolean)\n\n if (!roleNames.length) {\n console.log('No roles provided, nothing to seed.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n\n await seedDashboardDefaultsForTenant(em as EntityManager, {\n tenantId,\n organizationId,\n roleNames,\n widgetIds: widgetCsv ? widgetCsv.split(',').map((id) => id.trim()).filter(Boolean) : undefined,\n logger: (message) => console.log(message),\n })\n },\n}\n\nconst enableAnalyticsWidgets: ModuleCli = {\n command: 'enable-analytics-widgets',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = args.tenant || args.tenantId || null\n const organizationId = args.organization || args.organizationId || args.org || null\n const roleCsv = args.roles || 'admin,employee'\n if (!tenantId) {\n console.error('Usage: mercato dashboards enable-analytics-widgets --tenant <tenantId> [--org <orgId>] [--roles admin,employee]')\n return\n }\n\n const roleNames = roleCsv\n .split(',')\n .map((name) => name.trim())\n .filter(Boolean)\n\n if (!roleNames.length) {\n console.log('No roles provided, nothing to update.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const widgetIds = await resolveAnalyticsWidgetIds()\n\n const updated = await appendWidgetsToRoles(em, {\n tenantId,\n organizationId,\n roleNames,\n widgetIds,\n })\n\n if (!updated) {\n console.log('No dashboard role widgets updated.')\n }\n },\n}\n\nconst seedAnalytics: ModuleCli = {\n command: 'seed-analytics',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = args.tenant || args.tenantId || null\n const organizationId = args.organization || args.organizationId || args.org || null\n const months = args.months ? parseInt(args.months, 10) : 6\n const ordersPerMonth = args.ordersPerMonth ? parseInt(args.ordersPerMonth, 10) : 50\n\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato dashboards seed-analytics --tenant <tenantId> --organization <organizationId> [--months 6] [--ordersPerMonth 50]')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n console.log(`Seeding analytics data for ${months} months with ~${ordersPerMonth} orders/month...`)\n\n try {\n const result = await em.transactional(async (tem) =>\n seedAnalyticsData(tem, { tenantId, organizationId }, { months, ordersPerMonth })\n )\n\n if (result.orders === 0) {\n console.log('Analytics data already exists. Skipping seed.')\n } else {\n console.log(`Seeded analytics data:`)\n console.log(` - Orders: ${result.orders}`)\n console.log(` - Customers: ${result.customers}`)\n console.log(` - Products: ${result.products}`)\n console.log(` - Deals: ${result.deals}`)\n }\n } catch (error) {\n console.error('Failed to seed analytics data:', error)\n }\n },\n}\n\nconst debugAnalytics: ModuleCli = {\n command: 'debug-analytics',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = args.tenant || args.tenantId || null\n const organizationId = args.organization || args.organizationId || args.org || null\n\n if (!tenantId) {\n console.error('Usage: mercato dashboards debug-analytics --tenant <tenantId> [--organization <organizationId>]')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const conn = em.getConnection()\n\n console.log('Checking analytics data...\\n')\n\n const ordersResult = await conn.execute(\n `SELECT COUNT(*) as total, MIN(placed_at) as earliest, MAX(placed_at) as latest\n FROM sales_orders\n WHERE tenant_id = ? AND order_number LIKE 'SO-ANALYTICS-%'`,\n [tenantId]\n )\n console.log('Orders summary:', ordersResult[0])\n\n const recentOrders = await conn.execute(\n `SELECT order_number, placed_at, status, grand_total_gross_amount::numeric as total\n FROM sales_orders\n WHERE tenant_id = ? AND order_number LIKE 'SO-ANALYTICS-%'\n ORDER BY placed_at DESC LIMIT 5`,\n [tenantId]\n )\n console.log('\\nRecent orders:', recentOrders)\n\n const januaryOrders = await conn.execute(\n `SELECT COUNT(*) as count, SUM(grand_total_gross_amount::numeric) as total\n FROM sales_orders\n WHERE tenant_id = ?\n AND order_number LIKE 'SO-ANALYTICS-%'\n AND placed_at >= '2026-01-01'\n AND placed_at <= '2026-01-31 23:59:59'`,\n [tenantId]\n )\n console.log('\\nJanuary 2026 orders:', januaryOrders[0])\n\n const allOrders = await conn.execute(\n `SELECT COUNT(*) as count\n FROM sales_orders\n WHERE tenant_id = ?`,\n [tenantId]\n )\n console.log('\\nTotal orders in tenant:', allOrders[0])\n\n const orgCheck = await conn.execute(\n `SELECT organization_id, COUNT(*) as count\n FROM sales_orders\n WHERE tenant_id = ? AND order_number LIKE 'SO-ANALYTICS-%'\n GROUP BY organization_id`,\n [tenantId]\n )\n console.log('\\nOrders by organization:', orgCheck)\n\n if (organizationId) {\n const orgOrders = await conn.execute(\n `SELECT COUNT(*) as count, SUM(grand_total_gross_amount::numeric) as total\n FROM sales_orders\n WHERE tenant_id = ?\n AND organization_id = ?\n AND placed_at >= '2026-01-01'\n AND placed_at <= '2026-01-31 23:59:59'`,\n [tenantId, organizationId]\n )\n console.log(`\\nJanuary orders for org ${organizationId}:`, orgOrders[0])\n }\n\n // Check for NULL placed_at\n const nullPlacedAt = await conn.execute(\n `SELECT COUNT(*) as count\n FROM sales_orders\n WHERE tenant_id = ? AND placed_at IS NULL`,\n [tenantId]\n )\n console.log('\\nOrders with NULL placed_at:', nullPlacedAt[0])\n\n // Check non-analytics orders\n const nonAnalytics = await conn.execute(\n `SELECT order_number, placed_at, status, organization_id, grand_total_gross_amount::numeric as total\n FROM sales_orders\n WHERE tenant_id = ? AND order_number NOT LIKE 'SO-ANALYTICS-%'\n ORDER BY placed_at DESC NULLS LAST LIMIT 10`,\n [tenantId]\n )\n console.log('\\nNon-analytics orders:', nonAnalytics)\n\n // Simulate widget query\n console.log('\\n--- Simulating widget query for this_month ---')\n const widgetQuery = await conn.execute(\n `SELECT COALESCE(SUM(grand_total_gross_amount::numeric), 0) AS value\n FROM sales_orders\n WHERE tenant_id = ?\n AND organization_id = ANY(?::uuid[])\n AND deleted_at IS NULL\n AND placed_at >= '2026-01-01'\n AND placed_at <= '2026-01-31 23:59:59'`,\n [tenantId, `{${organizationId}}`]\n )\n console.log('Widget query result (revenue sum):', widgetQuery[0])\n\n const widgetCountQuery = await conn.execute(\n `SELECT COUNT(*) AS value\n FROM sales_orders\n WHERE tenant_id = ?\n AND organization_id = ANY(?::uuid[])\n AND deleted_at IS NULL\n AND placed_at >= '2026-01-01'\n AND placed_at <= '2026-01-31 23:59:59'`,\n [tenantId, `{${organizationId}}`]\n )\n console.log('Widget query result (order count):', widgetCountQuery[0])\n },\n}\n\nexport default [seedDefaults, enableAnalyticsWidgets, seedAnalytics, debugAnalytics]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { DashboardRoleWidgets } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'\nimport { appendWidgetsToRoles, resolveAnalyticsWidgetIds } from '@open-mercato/core/modules/dashboards/lib/role-widgets'\nimport { seedAnalyticsData } from './seed/analytics'\n\ntype Args = Record<string, string>\n\nfunction parseArgs(rest: string[]): Args {\n const args: Args = {}\n for (let i = 0; i < rest.length; i += 2) {\n const key = rest[i]?.replace(/^--/, '')\n const value = rest[i + 1]\n if (key) args[key] = value ?? ''\n }\n return args\n}\n\nexport async function seedDashboardDefaultsForTenant(\n em: EntityManager,\n {\n tenantId,\n organizationId = null,\n roleNames = ['superadmin', 'admin', 'employee'],\n widgetIds,\n logger,\n }: {\n tenantId: string\n organizationId?: string | null\n roleNames?: string[]\n widgetIds?: string[]\n logger?: (message: string) => void\n },\n): Promise<boolean> {\n if (!tenantId) throw new Error('tenantId is required')\n const log = logger ?? (() => {})\n\n const widgets = await loadAllWidgets()\n const widgetMap = new Map(widgets.map((widget) => [widget.metadata.id, widget]))\n const resolvedWidgetIds = widgetIds && widgetIds.length\n ? widgetIds.filter((id) => widgetMap.has(id))\n : null\n const defaultWidgetIds = widgets\n .filter((widget) => widget.metadata.defaultEnabled)\n .map((widget) => widget.metadata.id)\n const allWidgetIds = widgets.map((widget) => widget.metadata.id)\n\n if (resolvedWidgetIds && resolvedWidgetIds.length === 0) {\n log('No widgets resolved for dashboard seeding.')\n return false\n }\n\n await em.transactional(async (tem) => {\n for (const roleName of roleNames) {\n const isAdminRole = roleName === 'admin' || roleName === 'superadmin'\n const roleWidgetIds = resolvedWidgetIds ?? (isAdminRole ? allWidgetIds : defaultWidgetIds)\n if (!roleWidgetIds.length) {\n log(`No widgets resolved for role \"${roleName}\".`)\n continue\n }\n const role = await tem.findOne(Role, { name: roleName })\n if (!role) {\n log(`Skipping role \"${roleName}\" (not found)`)\n continue\n }\n const existing = await tem.findOne(DashboardRoleWidgets, {\n roleId: String(role.id),\n tenantId,\n organizationId,\n deletedAt: null,\n })\n if (existing) {\n existing.widgetIdsJson = roleWidgetIds\n tem.persist(existing)\n log(`Updated dashboard widgets for role \"${roleName}\"`)\n } else {\n const record = tem.create(DashboardRoleWidgets, {\n roleId: String(role.id),\n tenantId,\n organizationId,\n widgetIdsJson: roleWidgetIds,\n createdAt: new Date(),\n updatedAt: null,\n deletedAt: null,\n })\n tem.persist(record)\n log(`Created dashboard widgets for role \"${roleName}\"`)\n }\n }\n })\n\n return true\n}\n\nconst seedDefaults: ModuleCli = {\n command: 'seed-defaults',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = args.tenant || args.tenantId || null\n const organizationId = args.organization || args.organizationId || null\n const roleCsv = args.roles || 'superadmin,admin,employee'\n const widgetCsv = args.widgets || ''\n if (!tenantId) {\n console.error('Usage: mercato dashboards seed-defaults --tenant <tenantId> [--roles superadmin,admin,employee] [--widgets id1,id2]')\n return\n }\n\n const roleNames = roleCsv\n .split(',')\n .map((name) => name.trim())\n .filter(Boolean)\n\n if (!roleNames.length) {\n console.log('No roles provided, nothing to seed.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n\n await seedDashboardDefaultsForTenant(em as EntityManager, {\n tenantId,\n organizationId,\n roleNames,\n widgetIds: widgetCsv ? widgetCsv.split(',').map((id) => id.trim()).filter(Boolean) : undefined,\n logger: (message) => console.log(message),\n })\n },\n}\n\nconst enableAnalyticsWidgets: ModuleCli = {\n command: 'enable-analytics-widgets',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = args.tenant || args.tenantId || null\n const organizationId = args.organization || args.organizationId || args.org || null\n const roleCsv = args.roles || 'admin,employee'\n if (!tenantId) {\n console.error('Usage: mercato dashboards enable-analytics-widgets --tenant <tenantId> [--org <orgId>] [--roles admin,employee]')\n return\n }\n\n const roleNames = roleCsv\n .split(',')\n .map((name) => name.trim())\n .filter(Boolean)\n\n if (!roleNames.length) {\n console.log('No roles provided, nothing to update.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const widgetIds = await resolveAnalyticsWidgetIds()\n\n const updated = await appendWidgetsToRoles(em, {\n tenantId,\n organizationId,\n roleNames,\n widgetIds,\n })\n\n if (!updated) {\n console.log('No dashboard role widgets updated.')\n }\n },\n}\n\nconst seedAnalytics: ModuleCli = {\n command: 'seed-analytics',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = args.tenant || args.tenantId || null\n const organizationId = args.organization || args.organizationId || args.org || null\n const months = args.months ? parseInt(args.months, 10) : 6\n const ordersPerMonth = args.ordersPerMonth ? parseInt(args.ordersPerMonth, 10) : 50\n\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato dashboards seed-analytics --tenant <tenantId> --organization <organizationId> [--months 6] [--ordersPerMonth 50]')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n console.log(`Seeding analytics data for ${months} months with ~${ordersPerMonth} orders/month...`)\n\n try {\n const result = await em.transactional(async (tem) =>\n seedAnalyticsData(tem, { tenantId, organizationId }, { months, ordersPerMonth })\n )\n\n if (result.orders === 0) {\n console.log('Analytics data already exists. Skipping seed.')\n } else {\n console.log(`Seeded analytics data:`)\n console.log(` - Orders: ${result.orders}`)\n console.log(` - Customers: ${result.customers}`)\n console.log(` - Products: ${result.products}`)\n console.log(` - Deals: ${result.deals}`)\n }\n } catch (error) {\n console.error('Failed to seed analytics data:', error)\n }\n },\n}\n\nconst debugAnalytics: ModuleCli = {\n command: 'debug-analytics',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = args.tenant || args.tenantId || null\n const organizationId = args.organization || args.organizationId || args.org || null\n\n if (!tenantId) {\n console.error('Usage: mercato dashboards debug-analytics --tenant <tenantId> [--organization <organizationId>]')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const conn = em.getConnection()\n\n console.log('Checking analytics data...\\n')\n\n const ordersResult = await conn.execute(\n `SELECT COUNT(*) as total, MIN(placed_at) as earliest, MAX(placed_at) as latest\n FROM sales_orders\n WHERE tenant_id = ? AND order_number LIKE 'SO-ANALYTICS-%'`,\n [tenantId]\n )\n console.log('Orders summary:', ordersResult[0])\n\n const recentOrders = await conn.execute(\n `SELECT order_number, placed_at, status, grand_total_gross_amount::numeric as total\n FROM sales_orders\n WHERE tenant_id = ? AND order_number LIKE 'SO-ANALYTICS-%'\n ORDER BY placed_at DESC LIMIT 5`,\n [tenantId]\n )\n console.log('\\nRecent orders:', recentOrders)\n\n const januaryOrders = await conn.execute(\n `SELECT COUNT(*) as count, SUM(grand_total_gross_amount::numeric) as total\n FROM sales_orders\n WHERE tenant_id = ?\n AND order_number LIKE 'SO-ANALYTICS-%'\n AND placed_at >= '2026-01-01'\n AND placed_at <= '2026-01-31 23:59:59'`,\n [tenantId]\n )\n console.log('\\nJanuary 2026 orders:', januaryOrders[0])\n\n const allOrders = await conn.execute(\n `SELECT COUNT(*) as count\n FROM sales_orders\n WHERE tenant_id = ?`,\n [tenantId]\n )\n console.log('\\nTotal orders in tenant:', allOrders[0])\n\n const orgCheck = await conn.execute(\n `SELECT organization_id, COUNT(*) as count\n FROM sales_orders\n WHERE tenant_id = ? AND order_number LIKE 'SO-ANALYTICS-%'\n GROUP BY organization_id`,\n [tenantId]\n )\n console.log('\\nOrders by organization:', orgCheck)\n\n if (organizationId) {\n const orgOrders = await conn.execute(\n `SELECT COUNT(*) as count, SUM(grand_total_gross_amount::numeric) as total\n FROM sales_orders\n WHERE tenant_id = ?\n AND organization_id = ?\n AND placed_at >= '2026-01-01'\n AND placed_at <= '2026-01-31 23:59:59'`,\n [tenantId, organizationId]\n )\n console.log(`\\nJanuary orders for org ${organizationId}:`, orgOrders[0])\n }\n\n // Check for NULL placed_at\n const nullPlacedAt = await conn.execute(\n `SELECT COUNT(*) as count\n FROM sales_orders\n WHERE tenant_id = ? AND placed_at IS NULL`,\n [tenantId]\n )\n console.log('\\nOrders with NULL placed_at:', nullPlacedAt[0])\n\n // Check non-analytics orders\n const nonAnalytics = await conn.execute(\n `SELECT order_number, placed_at, status, organization_id, grand_total_gross_amount::numeric as total\n FROM sales_orders\n WHERE tenant_id = ? AND order_number NOT LIKE 'SO-ANALYTICS-%'\n ORDER BY placed_at DESC NULLS LAST LIMIT 10`,\n [tenantId]\n )\n console.log('\\nNon-analytics orders:', nonAnalytics)\n\n // Simulate widget query\n console.log('\\n--- Simulating widget query for this_month ---')\n const widgetQuery = await conn.execute(\n `SELECT COALESCE(SUM(grand_total_gross_amount::numeric), 0) AS value\n FROM sales_orders\n WHERE tenant_id = ?\n AND organization_id = ANY(?::uuid[])\n AND deleted_at IS NULL\n AND placed_at >= '2026-01-01'\n AND placed_at <= '2026-01-31 23:59:59'`,\n [tenantId, `{${organizationId}}`]\n )\n console.log('Widget query result (revenue sum):', widgetQuery[0])\n\n const widgetCountQuery = await conn.execute(\n `SELECT COUNT(*) AS value\n FROM sales_orders\n WHERE tenant_id = ?\n AND organization_id = ANY(?::uuid[])\n AND deleted_at IS NULL\n AND placed_at >= '2026-01-01'\n AND placed_at <= '2026-01-31 23:59:59'`,\n [tenantId, `{${organizationId}}`]\n )\n console.log('Widget query result (order count):', widgetCountQuery[0])\n },\n}\n\nexport default [seedDefaults, enableAnalyticsWidgets, seedAnalytics, debugAnalytics]\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,7 +7,7 @@ import { apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/u
7
7
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
8
8
  import { useT } from "@open-mercato/shared/lib/i18n/context";
9
9
  const EMPTY = [];
10
- function WidgetVisibilityEditor(props) {
10
+ const WidgetVisibilityEditor = React.forwardRef(function WidgetVisibilityEditor2(props, ref) {
11
11
  const t = useT();
12
12
  const { kind, targetId, tenantId, organizationId } = props;
13
13
  const [catalog, setCatalog] = React.useState([]);
@@ -19,6 +19,14 @@ function WidgetVisibilityEditor(props) {
19
19
  const [mode, setMode] = React.useState("inherit");
20
20
  const [originalMode, setOriginalMode] = React.useState("inherit");
21
21
  const [effective, setEffective] = React.useState(EMPTY);
22
+ const dirty = React.useMemo(() => {
23
+ if (kind === "user") {
24
+ if (mode !== originalMode) return true;
25
+ if (mode === "override") return selected.join("|") !== original.join("|");
26
+ return false;
27
+ }
28
+ return selected.join("|") !== original.join("|");
29
+ }, [kind, mode, original, originalMode, selected]);
22
30
  const loadCatalog = React.useCallback(async () => {
23
31
  const data = await readApiResultOrThrow(
24
32
  "/api/dashboards/widgets/catalog",
@@ -100,6 +108,9 @@ function WidgetVisibilityEditor(props) {
100
108
  setMode(originalMode);
101
109
  }, [original, originalMode]);
102
110
  const save = React.useCallback(async () => {
111
+ if (loading) return;
112
+ if (error && catalog.length === 0) return;
113
+ if (!dirty) return;
103
114
  setSaving(true);
104
115
  setError(null);
105
116
  try {
@@ -156,15 +167,8 @@ function WidgetVisibilityEditor(props) {
156
167
  } finally {
157
168
  setSaving(false);
158
169
  }
159
- }, [kind, mode, organizationId, selected, t, targetId, tenantId]);
160
- const dirty = React.useMemo(() => {
161
- if (kind === "user") {
162
- if (mode !== originalMode) return true;
163
- if (mode === "override") return selected.join("|") !== original.join("|");
164
- return false;
165
- }
166
- return selected.join("|") !== original.join("|");
167
- }, [kind, mode, original, originalMode, selected]);
170
+ }, [catalog.length, dirty, error, kind, loading, mode, organizationId, selected, t, targetId, tenantId]);
171
+ React.useImperativeHandle(ref, () => ({ save }), [save]);
168
172
  if (loading) {
169
173
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
170
174
  /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
@@ -230,7 +234,8 @@ function WidgetVisibilityEditor(props) {
230
234
  /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: resetSelections, disabled: !dirty, children: "Reset" })
231
235
  ] })
232
236
  ] });
233
- }
237
+ });
238
+ WidgetVisibilityEditor.displayName = "WidgetVisibilityEditor";
234
239
  export {
235
240
  WidgetVisibilityEditor
236
241
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/dashboards/components/WidgetVisibilityEditor.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype WidgetCatalogItem = {\n id: string\n title: string\n description: string | null\n}\n\ntype RoleResponse = {\n widgetIds: string[]\n hasCustom: boolean\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype UserResponse = {\n mode: 'inherit' | 'override'\n widgetIds: string[]\n hasCustom: boolean\n effectiveWidgetIds: string[]\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype BaseProps = {\n tenantId?: string | null\n organizationId?: string | null\n}\n\ntype RoleProps = BaseProps & {\n kind: 'role'\n targetId: string\n}\n\ntype UserProps = BaseProps & {\n kind: 'user'\n targetId: string\n}\n\ntype WidgetVisibilityEditorProps = RoleProps | UserProps\n\nconst EMPTY: string[] = []\n\nexport function WidgetVisibilityEditor(props: WidgetVisibilityEditorProps) {\n const t = useT()\n const { kind, targetId, tenantId, organizationId } = props\n const [catalog, setCatalog] = React.useState<WidgetCatalogItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const [selected, setSelected] = React.useState<string[]>(EMPTY)\n const [original, setOriginal] = React.useState<string[]>(EMPTY)\n const [mode, setMode] = React.useState<'inherit' | 'override'>('inherit')\n const [originalMode, setOriginalMode] = React.useState<'inherit' | 'override'>('inherit')\n const [effective, setEffective] = React.useState<string[]>(EMPTY)\n\n const loadCatalog = React.useCallback(async () => {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n '/api/dashboards/widgets/catalog',\n undefined,\n { errorMessage: t('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n const mapped = items\n .map((item: unknown): WidgetCatalogItem | null => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const id = typeof entry.id === 'string' ? entry.id : null\n if (!id || !id.length) return null\n const title =\n typeof entry.title === 'string' && entry.title.length ? entry.title : id\n const description =\n typeof entry.description === 'string' && entry.description.length ? entry.description : null\n return { id, title, description }\n })\n .filter((item: WidgetCatalogItem | null): item is WidgetCatalogItem => item !== null)\n setCatalog(mapped)\n }, [t])\n\n const loadRoleData = React.useCallback(async () => {\n const params = new URLSearchParams({ roleId: targetId })\n if (tenantId) params.set('tenantId', tenantId)\n if (organizationId) params.set('organizationId', organizationId)\n const data = await readApiResultOrThrow<RoleResponse>(\n `/api/dashboards/roles/widgets?${params.toString()}`,\n undefined,\n { errorMessage: t('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode('override')\n setOriginalMode('override')\n setEffective(ids)\n }, [organizationId, targetId, tenantId, t])\n\n const loadUserData = React.useCallback(async () => {\n const params = new URLSearchParams({ userId: targetId })\n if (tenantId) params.set('tenantId', tenantId)\n if (organizationId) params.set('organizationId', organizationId)\n const data = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?${params.toString()}`,\n undefined,\n { errorMessage: t('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode(data.mode || 'inherit')\n setOriginalMode(data.mode || 'inherit')\n setEffective(Array.isArray(data.effectiveWidgetIds) ? data.effectiveWidgetIds : [])\n }, [organizationId, targetId, tenantId, t])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n await loadCatalog()\n if (kind === 'role') await loadRoleData()\n else await loadUserData()\n } catch (err) {\n console.error('Failed to load widget visibility data', err)\n if (!cancelled) {\n setError(t('dashboards.widgets.error.load', 'Unable to load widget configuration.'))\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [kind, loadCatalog, loadRoleData, loadUserData, t])\n\n const toggle = React.useCallback((id: string) => {\n setSelected((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]))\n }, [])\n\n const resetSelections = React.useCallback(() => {\n setSelected(original)\n setMode(originalMode)\n }, [original, originalMode])\n\n const save = React.useCallback(async () => {\n setSaving(true)\n setError(null)\n try {\n const saveError = t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.')\n if (kind === 'role') {\n const payload = {\n roleId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n widgetIds: selected,\n }\n await apiCallOrThrow('/api/dashboards/roles/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError })\n setOriginal(selected)\n setOriginalMode('override')\n setEffective(selected)\n } else {\n const payload = {\n userId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n mode,\n widgetIds: selected,\n }\n await apiCallOrThrow('/api/dashboards/users/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError })\n setOriginal(selected)\n if (mode === 'inherit') {\n const refreshed = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?userId=${encodeURIComponent(targetId)}`,\n undefined,\n { errorMessage: saveError },\n )\n setEffective(Array.isArray(refreshed.effectiveWidgetIds) ? refreshed.effectiveWidgetIds : [])\n } else {\n setEffective(selected)\n }\n setOriginal(selected)\n setOriginalMode(mode)\n }\n try { flash(t('dashboards.widgets.flash.saved', 'Dashboard widgets updated'), 'success') } catch {}\n } catch (err) {\n console.error('Failed to save widget visibility', err)\n setError(t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.'))\n } finally {\n setSaving(false)\n }\n }, [kind, mode, organizationId, selected, t, targetId, tenantId])\n\n const dirty = React.useMemo(() => {\n if (kind === 'user') {\n if (mode !== originalMode) return true\n if (mode === 'override') return selected.join('|') !== original.join('|')\n return false\n }\n return selected.join('|') !== original.join('|')\n }, [kind, mode, original, originalMode, selected])\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" /> {t('dashboards.widgets.loading', 'Loading widget options\u2026')}\n </div>\n )\n }\n\n if (error && catalog.length === 0) {\n return <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n }\n\n return (\n <div className=\"space-y-4\">\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n )}\n\n {kind === 'user' && (\n <div className=\"flex items-center gap-3 rounded-md border bg-muted/30 px-3 py-2\">\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"widgetOverride\"\n value=\"inherit\"\n checked={mode === 'inherit'}\n onChange={() => setMode('inherit')}\n />\n {t('dashboards.widgets.mode.inherit', 'Inherit from roles')}\n </label>\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"widgetOverride\"\n value=\"override\"\n checked={mode === 'override'}\n onChange={() => setMode('override')}\n />\n {t('dashboards.widgets.mode.override', 'Override for this user')}\n </label>\n </div>\n )}\n\n {kind === 'user' && mode === 'inherit' && (\n <div className=\"rounded-md border border-muted bg-muted/20 px-3 py-2 text-xs text-muted-foreground\">\n {t('dashboards.widgets.mode.hint', 'This user currently inherits widgets from their assigned roles. Switch to override to customize.')}\n </div>\n )}\n\n {(kind === 'role' || mode === 'override') && (\n <div className=\"space-y-3\">\n {catalog.map((widget) => (\n <label key={widget.id} className=\"flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40\">\n <input\n type=\"checkbox\"\n className=\"mt-1 size-4\"\n checked={selected.includes(widget.id)}\n onChange={() => toggle(widget.id)}\n />\n <div>\n <div className=\"text-sm font-medium leading-none\">{widget.title}</div>\n {widget.description ? <div className=\"mt-1 text-xs text-muted-foreground\">{widget.description}</div> : null}\n </div>\n </label>\n ))}\n </div>\n )}\n\n {kind === 'user' && effective.length > 0 && (\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n Effective widgets: {effective.map((id) => catalog.find((meta) => meta.id === id)?.title || id).join(', ')}\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" onClick={save} disabled={saving || !dirty}>\n {saving ? 'Saving\u2026' : 'Save widgets'}\n </Button>\n <Button type=\"button\" variant=\"ghost\" onClick={resetSelections} disabled={!dirty}>\n Reset\n </Button>\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype WidgetCatalogItem = {\n id: string\n title: string\n description: string | null\n}\n\ntype RoleResponse = {\n widgetIds: string[]\n hasCustom: boolean\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype UserResponse = {\n mode: 'inherit' | 'override'\n widgetIds: string[]\n hasCustom: boolean\n effectiveWidgetIds: string[]\n scope: { tenantId: string | null; organizationId: string | null }\n}\n\ntype BaseProps = {\n tenantId?: string | null\n organizationId?: string | null\n}\n\ntype RoleProps = BaseProps & {\n kind: 'role'\n targetId: string\n}\n\ntype UserProps = BaseProps & {\n kind: 'user'\n targetId: string\n}\n\ntype WidgetVisibilityEditorProps = RoleProps | UserProps\n\nexport type WidgetVisibilityEditorHandle = {\n save: () => Promise<void>\n}\n\nconst EMPTY: string[] = []\n\nexport const WidgetVisibilityEditor = React.forwardRef<WidgetVisibilityEditorHandle, WidgetVisibilityEditorProps>(function WidgetVisibilityEditor(props, ref) {\n const t = useT()\n const { kind, targetId, tenantId, organizationId } = props\n const [catalog, setCatalog] = React.useState<WidgetCatalogItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const [selected, setSelected] = React.useState<string[]>(EMPTY)\n const [original, setOriginal] = React.useState<string[]>(EMPTY)\n const [mode, setMode] = React.useState<'inherit' | 'override'>('inherit')\n const [originalMode, setOriginalMode] = React.useState<'inherit' | 'override'>('inherit')\n const [effective, setEffective] = React.useState<string[]>(EMPTY)\n\n const dirty = React.useMemo(() => {\n if (kind === 'user') {\n if (mode !== originalMode) return true\n if (mode === 'override') return selected.join('|') !== original.join('|')\n return false\n }\n return selected.join('|') !== original.join('|')\n }, [kind, mode, original, originalMode, selected])\n\n const loadCatalog = React.useCallback(async () => {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n '/api/dashboards/widgets/catalog',\n undefined,\n { errorMessage: t('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n const mapped = items\n .map((item: unknown): WidgetCatalogItem | null => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const id = typeof entry.id === 'string' ? entry.id : null\n if (!id || !id.length) return null\n const title =\n typeof entry.title === 'string' && entry.title.length ? entry.title : id\n const description =\n typeof entry.description === 'string' && entry.description.length ? entry.description : null\n return { id, title, description }\n })\n .filter((item: WidgetCatalogItem | null): item is WidgetCatalogItem => item !== null)\n setCatalog(mapped)\n }, [t])\n\n const loadRoleData = React.useCallback(async () => {\n const params = new URLSearchParams({ roleId: targetId })\n if (tenantId) params.set('tenantId', tenantId)\n if (organizationId) params.set('organizationId', organizationId)\n const data = await readApiResultOrThrow<RoleResponse>(\n `/api/dashboards/roles/widgets?${params.toString()}`,\n undefined,\n { errorMessage: t('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode('override')\n setOriginalMode('override')\n setEffective(ids)\n }, [organizationId, targetId, tenantId, t])\n\n const loadUserData = React.useCallback(async () => {\n const params = new URLSearchParams({ userId: targetId })\n if (tenantId) params.set('tenantId', tenantId)\n if (organizationId) params.set('organizationId', organizationId)\n const data = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?${params.toString()}`,\n undefined,\n { errorMessage: t('dashboards.widgets.error.load', 'Unable to load widget configuration.') },\n )\n const ids = Array.isArray(data.widgetIds) ? data.widgetIds : []\n setSelected(ids)\n setOriginal(ids)\n setMode(data.mode || 'inherit')\n setOriginalMode(data.mode || 'inherit')\n setEffective(Array.isArray(data.effectiveWidgetIds) ? data.effectiveWidgetIds : [])\n }, [organizationId, targetId, tenantId, t])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n await loadCatalog()\n if (kind === 'role') await loadRoleData()\n else await loadUserData()\n } catch (err) {\n console.error('Failed to load widget visibility data', err)\n if (!cancelled) {\n setError(t('dashboards.widgets.error.load', 'Unable to load widget configuration.'))\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [kind, loadCatalog, loadRoleData, loadUserData, t])\n\n const toggle = React.useCallback((id: string) => {\n setSelected((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]))\n }, [])\n\n const resetSelections = React.useCallback(() => {\n setSelected(original)\n setMode(originalMode)\n }, [original, originalMode])\n\n const save = React.useCallback(async () => {\n if (loading) return\n if (error && catalog.length === 0) return\n if (!dirty) return\n setSaving(true)\n setError(null)\n try {\n const saveError = t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.')\n if (kind === 'role') {\n const payload = {\n roleId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n widgetIds: selected,\n }\n await apiCallOrThrow('/api/dashboards/roles/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError })\n setOriginal(selected)\n setOriginalMode('override')\n setEffective(selected)\n } else {\n const payload = {\n userId: targetId,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n mode,\n widgetIds: selected,\n }\n await apiCallOrThrow('/api/dashboards/users/widgets', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n }, { errorMessage: saveError })\n setOriginal(selected)\n if (mode === 'inherit') {\n const refreshed = await readApiResultOrThrow<UserResponse>(\n `/api/dashboards/users/widgets?userId=${encodeURIComponent(targetId)}`,\n undefined,\n { errorMessage: saveError },\n )\n setEffective(Array.isArray(refreshed.effectiveWidgetIds) ? refreshed.effectiveWidgetIds : [])\n } else {\n setEffective(selected)\n }\n setOriginal(selected)\n setOriginalMode(mode)\n }\n try { flash(t('dashboards.widgets.flash.saved', 'Dashboard widgets updated'), 'success') } catch {}\n } catch (err) {\n console.error('Failed to save widget visibility', err)\n setError(t('dashboards.widgets.error.save', 'Unable to save dashboard widget preferences.'))\n } finally {\n setSaving(false)\n }\n }, [catalog.length, dirty, error, kind, loading, mode, organizationId, selected, t, targetId, tenantId])\n\n React.useImperativeHandle(ref, () => ({ save }), [save])\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" /> {t('dashboards.widgets.loading', 'Loading widget options\u2026')}\n </div>\n )\n }\n\n if (error && catalog.length === 0) {\n return <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n }\n\n return (\n <div className=\"space-y-4\">\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n )}\n\n {kind === 'user' && (\n <div className=\"flex items-center gap-3 rounded-md border bg-muted/30 px-3 py-2\">\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"widgetOverride\"\n value=\"inherit\"\n checked={mode === 'inherit'}\n onChange={() => setMode('inherit')}\n />\n {t('dashboards.widgets.mode.inherit', 'Inherit from roles')}\n </label>\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"widgetOverride\"\n value=\"override\"\n checked={mode === 'override'}\n onChange={() => setMode('override')}\n />\n {t('dashboards.widgets.mode.override', 'Override for this user')}\n </label>\n </div>\n )}\n\n {kind === 'user' && mode === 'inherit' && (\n <div className=\"rounded-md border border-muted bg-muted/20 px-3 py-2 text-xs text-muted-foreground\">\n {t('dashboards.widgets.mode.hint', 'This user currently inherits widgets from their assigned roles. Switch to override to customize.')}\n </div>\n )}\n\n {(kind === 'role' || mode === 'override') && (\n <div className=\"space-y-3\">\n {catalog.map((widget) => (\n <label key={widget.id} className=\"flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40\">\n <input\n type=\"checkbox\"\n className=\"mt-1 size-4\"\n checked={selected.includes(widget.id)}\n onChange={() => toggle(widget.id)}\n />\n <div>\n <div className=\"text-sm font-medium leading-none\">{widget.title}</div>\n {widget.description ? <div className=\"mt-1 text-xs text-muted-foreground\">{widget.description}</div> : null}\n </div>\n </label>\n ))}\n </div>\n )}\n\n {kind === 'user' && effective.length > 0 && (\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n Effective widgets: {effective.map((id) => catalog.find((meta) => meta.id === id)?.title || id).join(', ')}\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" onClick={save} disabled={saving || !dirty}>\n {saving ? 'Saving\u2026' : 'Save widgets'}\n </Button>\n <Button type=\"button\" variant=\"ghost\" onClick={resetSelections} disabled={!dirty}>\n Reset\n </Button>\n </div>\n </div>\n )\n})\n\nWidgetVisibilityEditor.displayName = 'WidgetVisibilityEditor'\n"],
5
+ "mappings": "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",
6
+ "names": ["WidgetVisibilityEditor"]
7
7
  }
@@ -1,4 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { resolveTenantEncryptionService } from "@open-mercato/shared/lib/encryption/customFieldValues";
3
+ import { resolveEntityIdFromMetadata } from "@open-mercato/shared/lib/encryption/entityIds";
2
4
  import {
3
5
  resolveDateRange,
4
6
  getPreviousPeriod,
@@ -178,10 +180,27 @@ class WidgetDataService {
178
180
  )}`;
179
181
  try {
180
182
  const labelRows = await this.em.getConnection().execute(sql, params);
183
+ const meta = this.resolveEntityMetadata(config.table);
184
+ const entityId = this.resolveEntityId(meta);
185
+ const encryptionService = resolveTenantEncryptionService(this.em);
186
+ const organizationId = this.resolveOrganizationId();
181
187
  const labelMap = /* @__PURE__ */ new Map();
182
188
  for (const row of labelRows) {
183
- if (row.id && row.label != null && row.label !== "") {
184
- labelMap.set(row.id, row.label);
189
+ let labelValue = row.label;
190
+ if (entityId && encryptionService?.isEnabled() && labelValue != null) {
191
+ const decrypted = await encryptionService.decryptEntityPayload(
192
+ entityId,
193
+ { [config.labelColumn]: labelValue },
194
+ this.scope.tenantId,
195
+ organizationId
196
+ );
197
+ const resolved = decrypted[config.labelColumn];
198
+ if (typeof resolved === "string" || typeof resolved === "number") {
199
+ labelValue = String(resolved);
200
+ }
201
+ }
202
+ if (row.id && labelValue != null && labelValue !== "") {
203
+ labelMap.set(row.id, labelValue);
185
204
  }
186
205
  }
187
206
  return data.map((item) => ({
@@ -195,6 +214,31 @@ class WidgetDataService {
195
214
  }));
196
215
  }
197
216
  }
217
+ resolveOrganizationId() {
218
+ if (!this.scope.organizationIds || this.scope.organizationIds.length !== 1) return null;
219
+ return this.scope.organizationIds[0] ?? null;
220
+ }
221
+ resolveEntityMetadata(tableName) {
222
+ const registry = this.em?.getMetadata?.();
223
+ if (!registry) return null;
224
+ const entries = typeof registry.getAll === "function" && registry.getAll() || (Array.isArray(registry.metadata) ? registry.metadata : Object.values(registry.metadata ?? {}));
225
+ const metas = Array.isArray(entries) ? entries : Object.values(entries ?? {});
226
+ const match = metas.find((meta) => {
227
+ const table = meta?.tableName ?? meta?.collection;
228
+ if (typeof table !== "string") return false;
229
+ if (table === tableName) return true;
230
+ return table.split(".").pop() === tableName;
231
+ });
232
+ return match ?? null;
233
+ }
234
+ resolveEntityId(meta) {
235
+ if (!meta) return null;
236
+ try {
237
+ return resolveEntityIdFromMetadata(meta);
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
198
242
  }
199
243
  function createWidgetDataService(em, scope, registry, cache) {
200
244
  return new WidgetDataService({ em, scope, registry, cache });