@open-mercato/core 0.4.2-canary-10c7a8bf2a → 0.4.2-canary-e6bf6a353e

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 (136) hide show
  1. package/dist/modules/auth/lib/setup-app.js +2 -0
  2. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  3. package/dist/modules/catalog/analytics.js +27 -0
  4. package/dist/modules/catalog/analytics.js.map +7 -0
  5. package/dist/modules/customers/analytics.js +50 -0
  6. package/dist/modules/customers/analytics.js.map +7 -0
  7. package/dist/modules/dashboards/acl.js +2 -1
  8. package/dist/modules/dashboards/acl.js.map +2 -2
  9. package/dist/modules/dashboards/api/widgets/data/route.js +187 -0
  10. package/dist/modules/dashboards/api/widgets/data/route.js.map +7 -0
  11. package/dist/modules/dashboards/cli.js +142 -1
  12. package/dist/modules/dashboards/cli.js.map +2 -2
  13. package/dist/modules/dashboards/di.js +11 -0
  14. package/dist/modules/dashboards/di.js.map +7 -0
  15. package/dist/modules/dashboards/lib/aggregations.js +162 -0
  16. package/dist/modules/dashboards/lib/aggregations.js.map +7 -0
  17. package/dist/modules/dashboards/lib/formatters.js +34 -0
  18. package/dist/modules/dashboards/lib/formatters.js.map +7 -0
  19. package/dist/modules/dashboards/seed/analytics.js +383 -0
  20. package/dist/modules/dashboards/seed/analytics.js.map +7 -0
  21. package/dist/modules/dashboards/services/analyticsRegistry.js +52 -0
  22. package/dist/modules/dashboards/services/analyticsRegistry.js.map +7 -0
  23. package/dist/modules/dashboards/services/widgetDataService.js +207 -0
  24. package/dist/modules/dashboards/services/widgetDataService.js.map +7 -0
  25. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/config.js +18 -0
  26. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/config.js.map +7 -0
  27. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js +128 -0
  28. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js.map +7 -0
  29. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.js +25 -0
  30. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.js.map +7 -0
  31. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/config.js +18 -0
  32. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/config.js.map +7 -0
  33. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js +126 -0
  34. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js.map +7 -0
  35. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.js +25 -0
  36. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.js.map +7 -0
  37. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/config.js +18 -0
  38. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/config.js.map +7 -0
  39. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +151 -0
  40. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +7 -0
  41. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.js +25 -0
  42. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.js.map +7 -0
  43. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/config.js +18 -0
  44. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/config.js.map +7 -0
  45. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js +126 -0
  46. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js.map +7 -0
  47. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.js +25 -0
  48. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.js.map +7 -0
  49. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js +16 -0
  50. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js.map +7 -0
  51. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js +123 -0
  52. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js.map +7 -0
  53. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js +25 -0
  54. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js.map +7 -0
  55. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/config.js +18 -0
  56. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/config.js.map +7 -0
  57. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js +128 -0
  58. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js.map +7 -0
  59. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.js +25 -0
  60. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.js.map +7 -0
  61. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/config.js +21 -0
  62. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/config.js.map +7 -0
  63. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +211 -0
  64. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +7 -0
  65. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.js +25 -0
  66. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.js.map +7 -0
  67. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/config.js +19 -0
  68. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/config.js.map +7 -0
  69. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +131 -0
  70. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +7 -0
  71. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.js +25 -0
  72. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.js.map +7 -0
  73. package/dist/modules/dashboards/widgets/dashboard/top-customers/config.js +19 -0
  74. package/dist/modules/dashboards/widgets/dashboard/top-customers/config.js.map +7 -0
  75. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +153 -0
  76. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +7 -0
  77. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.js +25 -0
  78. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.js.map +7 -0
  79. package/dist/modules/dashboards/widgets/dashboard/top-products/config.js +22 -0
  80. package/dist/modules/dashboards/widgets/dashboard/top-products/config.js.map +7 -0
  81. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +180 -0
  82. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +7 -0
  83. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.js +25 -0
  84. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.js.map +7 -0
  85. package/dist/modules/sales/analytics.js +67 -0
  86. package/dist/modules/sales/analytics.js.map +7 -0
  87. package/package.json +2 -2
  88. package/src/modules/auth/lib/setup-app.ts +2 -0
  89. package/src/modules/catalog/analytics.ts +24 -0
  90. package/src/modules/customers/analytics.ts +47 -0
  91. package/src/modules/dashboards/acl.ts +1 -0
  92. package/src/modules/dashboards/api/widgets/data/route.ts +221 -0
  93. package/src/modules/dashboards/cli.ts +164 -1
  94. package/src/modules/dashboards/di.ts +9 -0
  95. package/src/modules/dashboards/i18n/de.json +115 -1
  96. package/src/modules/dashboards/i18n/en.json +115 -1
  97. package/src/modules/dashboards/i18n/es.json +115 -1
  98. package/src/modules/dashboards/i18n/pl.json +115 -1
  99. package/src/modules/dashboards/lib/__tests__/aggregations.test.ts +327 -0
  100. package/src/modules/dashboards/lib/__tests__/formatters.test.ts +128 -0
  101. package/src/modules/dashboards/lib/aggregations.ts +225 -0
  102. package/src/modules/dashboards/lib/formatters.ts +36 -0
  103. package/src/modules/dashboards/seed/analytics.ts +405 -0
  104. package/src/modules/dashboards/services/analyticsRegistry.ts +79 -0
  105. package/src/modules/dashboards/services/widgetDataService.ts +329 -0
  106. package/src/modules/dashboards/widgets/dashboard/aov-kpi/config.ts +20 -0
  107. package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.tsx +135 -0
  108. package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.ts +24 -0
  109. package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/config.ts +20 -0
  110. package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.tsx +133 -0
  111. package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.ts +24 -0
  112. package/src/modules/dashboards/widgets/dashboard/orders-by-status/config.ts +20 -0
  113. package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +154 -0
  114. package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.ts +24 -0
  115. package/src/modules/dashboards/widgets/dashboard/orders-kpi/config.ts +20 -0
  116. package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.tsx +133 -0
  117. package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.ts +24 -0
  118. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/config.ts +17 -0
  119. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.tsx +137 -0
  120. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.ts +24 -0
  121. package/src/modules/dashboards/widgets/dashboard/revenue-kpi/config.ts +20 -0
  122. package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.tsx +135 -0
  123. package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.ts +24 -0
  124. package/src/modules/dashboards/widgets/dashboard/revenue-trend/config.ts +24 -0
  125. package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +220 -0
  126. package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.ts +24 -0
  127. package/src/modules/dashboards/widgets/dashboard/sales-by-region/config.ts +21 -0
  128. package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +131 -0
  129. package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.ts +24 -0
  130. package/src/modules/dashboards/widgets/dashboard/top-customers/config.ts +21 -0
  131. package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +161 -0
  132. package/src/modules/dashboards/widgets/dashboard/top-customers/widget.ts +24 -0
  133. package/src/modules/dashboards/widgets/dashboard/top-products/config.ts +27 -0
  134. package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +181 -0
  135. package/src/modules/dashboards/widgets/dashboard/top-products/widget.ts +24 -0
  136. package/src/modules/sales/analytics.ts +64 -0
@@ -2,6 +2,7 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
2
2
  import { DashboardRoleWidgets } from "@open-mercato/core/modules/dashboards/data/entities";
3
3
  import { Role } from "@open-mercato/core/modules/auth/data/entities";
4
4
  import { loadAllWidgets } from "@open-mercato/core/modules/dashboards/lib/widgets";
5
+ import { seedAnalyticsData } from "./seed/analytics.js";
5
6
  function parseArgs(rest) {
6
7
  const args = {};
7
8
  for (let i = 0; i < rest.length; i += 2) {
@@ -90,7 +91,147 @@ const seedDefaults = {
90
91
  });
91
92
  }
92
93
  };
93
- var cli_default = [seedDefaults];
94
+ const seedAnalytics = {
95
+ command: "seed-analytics",
96
+ async run(rest) {
97
+ const args = parseArgs(rest);
98
+ const tenantId = args.tenant || args.tenantId || null;
99
+ const organizationId = args.organization || args.organizationId || args.org || null;
100
+ const months = args.months ? parseInt(args.months, 10) : 6;
101
+ const ordersPerMonth = args.ordersPerMonth ? parseInt(args.ordersPerMonth, 10) : 50;
102
+ if (!tenantId || !organizationId) {
103
+ console.error("Usage: mercato dashboards seed-analytics --tenant <tenantId> --organization <organizationId> [--months 6] [--ordersPerMonth 50]");
104
+ return;
105
+ }
106
+ const { resolve } = await createRequestContainer();
107
+ const em = resolve("em");
108
+ console.log(`Seeding analytics data for ${months} months with ~${ordersPerMonth} orders/month...`);
109
+ try {
110
+ const result = await em.transactional(
111
+ async (tem) => seedAnalyticsData(tem, { tenantId, organizationId }, { months, ordersPerMonth })
112
+ );
113
+ if (result.orders === 0) {
114
+ console.log("Analytics data already exists. Skipping seed.");
115
+ } else {
116
+ console.log(`Seeded analytics data:`);
117
+ console.log(` - Orders: ${result.orders}`);
118
+ console.log(` - Customers: ${result.customers}`);
119
+ console.log(` - Products: ${result.products}`);
120
+ console.log(` - Deals: ${result.deals}`);
121
+ }
122
+ } catch (error) {
123
+ console.error("Failed to seed analytics data:", error);
124
+ }
125
+ }
126
+ };
127
+ const debugAnalytics = {
128
+ command: "debug-analytics",
129
+ async run(rest) {
130
+ const args = parseArgs(rest);
131
+ const tenantId = args.tenant || args.tenantId || null;
132
+ const organizationId = args.organization || args.organizationId || args.org || null;
133
+ if (!tenantId) {
134
+ console.error("Usage: mercato dashboards debug-analytics --tenant <tenantId> [--organization <organizationId>]");
135
+ return;
136
+ }
137
+ const { resolve } = await createRequestContainer();
138
+ const em = resolve("em");
139
+ const conn = em.getConnection();
140
+ console.log("Checking analytics data...\n");
141
+ const ordersResult = await conn.execute(
142
+ `SELECT COUNT(*) as total, MIN(placed_at) as earliest, MAX(placed_at) as latest
143
+ FROM sales_orders
144
+ WHERE tenant_id = ? AND order_number LIKE 'SO-ANALYTICS-%'`,
145
+ [tenantId]
146
+ );
147
+ console.log("Orders summary:", ordersResult[0]);
148
+ const recentOrders = await conn.execute(
149
+ `SELECT order_number, placed_at, status, grand_total_gross_amount::numeric as total
150
+ FROM sales_orders
151
+ WHERE tenant_id = ? AND order_number LIKE 'SO-ANALYTICS-%'
152
+ ORDER BY placed_at DESC LIMIT 5`,
153
+ [tenantId]
154
+ );
155
+ console.log("\nRecent orders:", recentOrders);
156
+ const januaryOrders = await conn.execute(
157
+ `SELECT COUNT(*) as count, SUM(grand_total_gross_amount::numeric) as total
158
+ FROM sales_orders
159
+ WHERE tenant_id = ?
160
+ AND order_number LIKE 'SO-ANALYTICS-%'
161
+ AND placed_at >= '2026-01-01'
162
+ AND placed_at <= '2026-01-31 23:59:59'`,
163
+ [tenantId]
164
+ );
165
+ console.log("\nJanuary 2026 orders:", januaryOrders[0]);
166
+ const allOrders = await conn.execute(
167
+ `SELECT COUNT(*) as count
168
+ FROM sales_orders
169
+ WHERE tenant_id = ?`,
170
+ [tenantId]
171
+ );
172
+ console.log("\nTotal orders in tenant:", allOrders[0]);
173
+ const orgCheck = await conn.execute(
174
+ `SELECT organization_id, COUNT(*) as count
175
+ FROM sales_orders
176
+ WHERE tenant_id = ? AND order_number LIKE 'SO-ANALYTICS-%'
177
+ GROUP BY organization_id`,
178
+ [tenantId]
179
+ );
180
+ console.log("\nOrders by organization:", orgCheck);
181
+ if (organizationId) {
182
+ const orgOrders = await conn.execute(
183
+ `SELECT COUNT(*) as count, SUM(grand_total_gross_amount::numeric) as total
184
+ FROM sales_orders
185
+ WHERE tenant_id = ?
186
+ AND organization_id = ?
187
+ AND placed_at >= '2026-01-01'
188
+ AND placed_at <= '2026-01-31 23:59:59'`,
189
+ [tenantId, organizationId]
190
+ );
191
+ console.log(`
192
+ January orders for org ${organizationId}:`, orgOrders[0]);
193
+ }
194
+ const nullPlacedAt = await conn.execute(
195
+ `SELECT COUNT(*) as count
196
+ FROM sales_orders
197
+ WHERE tenant_id = ? AND placed_at IS NULL`,
198
+ [tenantId]
199
+ );
200
+ console.log("\nOrders with NULL placed_at:", nullPlacedAt[0]);
201
+ const nonAnalytics = await conn.execute(
202
+ `SELECT order_number, placed_at, status, organization_id, grand_total_gross_amount::numeric as total
203
+ FROM sales_orders
204
+ WHERE tenant_id = ? AND order_number NOT LIKE 'SO-ANALYTICS-%'
205
+ ORDER BY placed_at DESC NULLS LAST LIMIT 10`,
206
+ [tenantId]
207
+ );
208
+ console.log("\nNon-analytics orders:", nonAnalytics);
209
+ console.log("\n--- Simulating widget query for this_month ---");
210
+ const widgetQuery = await conn.execute(
211
+ `SELECT COALESCE(SUM(grand_total_gross_amount::numeric), 0) AS value
212
+ FROM sales_orders
213
+ WHERE tenant_id = ?
214
+ AND organization_id = ANY(?::uuid[])
215
+ AND deleted_at IS NULL
216
+ AND placed_at >= '2026-01-01'
217
+ AND placed_at <= '2026-01-31 23:59:59'`,
218
+ [tenantId, `{${organizationId}}`]
219
+ );
220
+ console.log("Widget query result (revenue sum):", widgetQuery[0]);
221
+ const widgetCountQuery = await conn.execute(
222
+ `SELECT COUNT(*) AS value
223
+ FROM sales_orders
224
+ WHERE tenant_id = ?
225
+ AND organization_id = ANY(?::uuid[])
226
+ AND deleted_at IS NULL
227
+ AND placed_at >= '2026-01-01'
228
+ AND placed_at <= '2026-01-31 23:59:59'`,
229
+ [tenantId, `{${organizationId}}`]
230
+ );
231
+ console.log("Widget query result (order count):", widgetCountQuery[0]);
232
+ }
233
+ };
234
+ var cli_default = [seedDefaults, seedAnalytics, debugAnalytics];
94
235
  export {
95
236
  cli_default as default,
96
237
  seedDashboardDefaultsForTenant
@@ -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'\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\nexport default [seedDefaults]\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 { 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 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, seedAnalytics, debugAnalytics]\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,11 @@
1
+ import { asFunction } from "awilix";
2
+ import { createAnalyticsRegistry } from "./services/analyticsRegistry.js";
3
+ function register(container) {
4
+ container.register({
5
+ analyticsRegistry: asFunction(() => createAnalyticsRegistry()).singleton()
6
+ });
7
+ }
8
+ export {
9
+ register
10
+ };
11
+ //# sourceMappingURL=di.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/dashboards/di.ts"],
4
+ "sourcesContent": ["import { asFunction } from 'awilix'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { createAnalyticsRegistry } from './services/analyticsRegistry'\n\nexport function register(container: AppContainer): void {\n container.register({\n analyticsRegistry: asFunction(() => createAnalyticsRegistry()).singleton(),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,kBAAkB;AAE3B,SAAS,+BAA+B;AAEjC,SAAS,SAAS,WAA+B;AACtD,YAAU,SAAS;AAAA,IACjB,mBAAmB,WAAW,MAAM,wBAAwB,CAAC,EAAE,UAAU;AAAA,EAC3E,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,162 @@
1
+ const VALID_GRANULARITIES = ["day", "week", "month", "quarter", "year"];
2
+ const VALID_AGGREGATES = ["count", "sum", "avg", "min", "max"];
3
+ const SAFE_IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
4
+ function isValidGranularity(value) {
5
+ return typeof value === "string" && VALID_GRANULARITIES.includes(value);
6
+ }
7
+ function isValidAggregate(value) {
8
+ return typeof value === "string" && VALID_AGGREGATES.includes(value);
9
+ }
10
+ function isSafeIdentifier(value) {
11
+ return SAFE_IDENTIFIER_PATTERN.test(value);
12
+ }
13
+ function buildAggregateExpression(aggregate, column) {
14
+ switch (aggregate) {
15
+ case "count":
16
+ return column === "id" ? "COUNT(*)" : `COUNT(${column})`;
17
+ case "sum":
18
+ return `COALESCE(SUM(${column}::numeric), 0)`;
19
+ case "avg":
20
+ return `COALESCE(AVG(${column}::numeric), 0)`;
21
+ case "min":
22
+ return `MIN(${column}::numeric)`;
23
+ case "max":
24
+ return `MAX(${column}::numeric)`;
25
+ default:
26
+ return `COUNT(*)`;
27
+ }
28
+ }
29
+ function buildDateTruncExpression(column, granularity) {
30
+ if (!isValidGranularity(granularity)) {
31
+ throw new Error(`Invalid granularity: ${granularity}`);
32
+ }
33
+ return `DATE_TRUNC('${granularity}', ${column})`;
34
+ }
35
+ function buildJsonbFieldExpression(column, path) {
36
+ const parts = path.split(".");
37
+ for (const part of parts) {
38
+ if (!isSafeIdentifier(part)) {
39
+ throw new Error(`Invalid JSONB path part: ${part}`);
40
+ }
41
+ }
42
+ if (parts.length === 1) {
43
+ return `${column}->>'${parts[0]}'`;
44
+ }
45
+ const intermediate = parts.slice(0, -1).map((p) => `'${p}'`).join("->");
46
+ const lastPart = parts[parts.length - 1];
47
+ return `${column}->${intermediate}->>'${lastPart}'`;
48
+ }
49
+ function buildAggregationQuery(options) {
50
+ const { registry } = options;
51
+ const config = registry.getEntityTypeConfig(options.entityType);
52
+ if (!config) return null;
53
+ const metricMapping = registry.getFieldMapping(options.entityType, options.metric.field);
54
+ if (!metricMapping) return null;
55
+ const params = [];
56
+ const tableName = config.schema ? `"${config.schema}"."${config.tableName}"` : `"${config.tableName}"`;
57
+ const aggregateExpr = buildAggregateExpression(options.metric.aggregate, metricMapping.dbColumn);
58
+ let selectClause = `SELECT ${aggregateExpr} AS value`;
59
+ let groupByClause = "";
60
+ let orderByClause = "";
61
+ let limitClause = "";
62
+ if (options.groupBy) {
63
+ let groupMapping = registry.getFieldMapping(options.entityType, options.groupBy.field);
64
+ let groupExpr = null;
65
+ if (!groupMapping && options.groupBy.field.includes(".")) {
66
+ const [baseField, ...pathParts] = options.groupBy.field.split(".");
67
+ const baseMapping = registry.getFieldMapping(options.entityType, baseField);
68
+ if (baseMapping?.type === "jsonb") {
69
+ groupExpr = buildJsonbFieldExpression(baseMapping.dbColumn, pathParts.join("."));
70
+ }
71
+ } else if (groupMapping) {
72
+ if (groupMapping.type === "timestamp" && options.groupBy.granularity) {
73
+ groupExpr = buildDateTruncExpression(groupMapping.dbColumn, options.groupBy.granularity);
74
+ } else {
75
+ groupExpr = groupMapping.dbColumn;
76
+ }
77
+ }
78
+ if (groupExpr) {
79
+ selectClause = `SELECT ${groupExpr} AS group_key, ${aggregateExpr} AS value`;
80
+ groupByClause = `GROUP BY ${groupExpr}`;
81
+ orderByClause = `ORDER BY value DESC`;
82
+ if (options.groupBy.limit && options.groupBy.limit > 0) {
83
+ limitClause = `LIMIT ${Math.min(options.groupBy.limit, 100)}`;
84
+ }
85
+ }
86
+ }
87
+ const whereClauses = [];
88
+ whereClauses.push(`tenant_id = ?`);
89
+ params.push(options.scope.tenantId);
90
+ if (options.scope.organizationIds && options.scope.organizationIds.length > 0) {
91
+ whereClauses.push(`organization_id = ANY(?::uuid[])`);
92
+ params.push(`{${options.scope.organizationIds.join(",")}}`);
93
+ }
94
+ whereClauses.push(`deleted_at IS NULL`);
95
+ if (options.dateRange) {
96
+ const dateMapping = registry.getFieldMapping(options.entityType, options.dateRange.field);
97
+ if (dateMapping) {
98
+ whereClauses.push(`${dateMapping.dbColumn} >= ?`);
99
+ params.push(options.dateRange.start);
100
+ whereClauses.push(`${dateMapping.dbColumn} <= ?`);
101
+ params.push(options.dateRange.end);
102
+ }
103
+ }
104
+ if (options.filters) {
105
+ for (const filter of options.filters) {
106
+ const filterMapping = registry.getFieldMapping(options.entityType, filter.field);
107
+ if (!filterMapping) continue;
108
+ switch (filter.operator) {
109
+ case "eq":
110
+ whereClauses.push(`${filterMapping.dbColumn} = ?`);
111
+ params.push(filter.value);
112
+ break;
113
+ case "neq":
114
+ whereClauses.push(`${filterMapping.dbColumn} != ?`);
115
+ params.push(filter.value);
116
+ break;
117
+ case "gt":
118
+ whereClauses.push(`${filterMapping.dbColumn} > ?`);
119
+ params.push(filter.value);
120
+ break;
121
+ case "gte":
122
+ whereClauses.push(`${filterMapping.dbColumn} >= ?`);
123
+ params.push(filter.value);
124
+ break;
125
+ case "lt":
126
+ whereClauses.push(`${filterMapping.dbColumn} < ?`);
127
+ params.push(filter.value);
128
+ break;
129
+ case "lte":
130
+ whereClauses.push(`${filterMapping.dbColumn} <= ?`);
131
+ params.push(filter.value);
132
+ break;
133
+ case "in":
134
+ whereClauses.push(`${filterMapping.dbColumn} = ANY(?)`);
135
+ params.push(filter.value);
136
+ break;
137
+ case "not_in":
138
+ whereClauses.push(`${filterMapping.dbColumn} != ALL(?)`);
139
+ params.push(filter.value);
140
+ break;
141
+ case "is_null":
142
+ whereClauses.push(`${filterMapping.dbColumn} IS NULL`);
143
+ break;
144
+ case "is_not_null":
145
+ whereClauses.push(`${filterMapping.dbColumn} IS NOT NULL`);
146
+ break;
147
+ }
148
+ }
149
+ }
150
+ const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
151
+ const sql = [selectClause, `FROM ${tableName}`, whereClause, groupByClause, orderByClause, limitClause].filter(Boolean).join(" ");
152
+ return { sql, params };
153
+ }
154
+ export {
155
+ buildAggregateExpression,
156
+ buildAggregationQuery,
157
+ buildDateTruncExpression,
158
+ buildJsonbFieldExpression,
159
+ isValidAggregate,
160
+ isValidGranularity
161
+ };
162
+ //# sourceMappingURL=aggregations.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/dashboards/lib/aggregations.ts"],
4
+ "sourcesContent": ["import type { AnalyticsRegistry } from '../services/analyticsRegistry'\nimport type { AnalyticsEntityTypeConfig, AnalyticsFieldMapping } from '@open-mercato/shared/modules/analytics'\n\nexport type AggregateFunction = 'count' | 'sum' | 'avg' | 'min' | 'max'\nexport type DateGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year'\n\nconst VALID_GRANULARITIES: readonly DateGranularity[] = ['day', 'week', 'month', 'quarter', 'year']\nconst VALID_AGGREGATES: readonly AggregateFunction[] = ['count', 'sum', 'avg', 'min', 'max']\nconst SAFE_IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/\n\nexport function isValidGranularity(value: unknown): value is DateGranularity {\n return typeof value === 'string' && VALID_GRANULARITIES.includes(value as DateGranularity)\n}\n\nexport function isValidAggregate(value: unknown): value is AggregateFunction {\n return typeof value === 'string' && VALID_AGGREGATES.includes(value as AggregateFunction)\n}\n\nfunction isSafeIdentifier(value: string): boolean {\n return SAFE_IDENTIFIER_PATTERN.test(value)\n}\n\n// Re-export types from shared module for convenience\nexport type EntityTypeConfig = AnalyticsEntityTypeConfig\nexport type FieldMapping = AnalyticsFieldMapping\n\nexport function buildAggregateExpression(aggregate: AggregateFunction, column: string): string {\n switch (aggregate) {\n case 'count':\n return column === 'id' ? 'COUNT(*)' : `COUNT(${column})`\n case 'sum':\n return `COALESCE(SUM(${column}::numeric), 0)`\n case 'avg':\n return `COALESCE(AVG(${column}::numeric), 0)`\n case 'min':\n return `MIN(${column}::numeric)`\n case 'max':\n return `MAX(${column}::numeric)`\n default:\n return `COUNT(*)`\n }\n}\n\nexport function buildDateTruncExpression(column: string, granularity: DateGranularity): string {\n if (!isValidGranularity(granularity)) {\n throw new Error(`Invalid granularity: ${granularity}`)\n }\n return `DATE_TRUNC('${granularity}', ${column})`\n}\n\nexport function buildJsonbFieldExpression(column: string, path: string): string {\n const parts = path.split('.')\n for (const part of parts) {\n if (!isSafeIdentifier(part)) {\n throw new Error(`Invalid JSONB path part: ${part}`)\n }\n }\n if (parts.length === 1) {\n return `${column}->>'${parts[0]}'`\n }\n const intermediate = parts.slice(0, -1).map((p) => `'${p}'`).join('->')\n const lastPart = parts[parts.length - 1]\n return `${column}->${intermediate}->>'${lastPart}'`\n}\n\nexport type AggregationQuery = {\n sql: string\n params: unknown[]\n}\n\nexport type BuildAggregationQueryOptions = {\n entityType: string\n metric: {\n field: string\n aggregate: AggregateFunction\n }\n groupBy?: {\n field: string\n granularity?: DateGranularity\n limit?: number\n }\n dateRange?: {\n field: string\n start: Date\n end: Date\n }\n filters?: Array<{\n field: string\n operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'not_in' | 'is_null' | 'is_not_null'\n value?: unknown\n }>\n scope: {\n tenantId: string\n organizationIds?: string[]\n }\n /** Analytics registry for resolving entity and field configurations */\n registry: AnalyticsRegistry\n}\n\nexport function buildAggregationQuery(options: BuildAggregationQueryOptions): AggregationQuery | null {\n const { registry } = options\n const config = registry.getEntityTypeConfig(options.entityType)\n if (!config) return null\n\n const metricMapping = registry.getFieldMapping(options.entityType, options.metric.field)\n if (!metricMapping) return null\n\n const params: unknown[] = []\n\n const tableName = config.schema ? `\"${config.schema}\".\"${config.tableName}\"` : `\"${config.tableName}\"`\n const aggregateExpr = buildAggregateExpression(options.metric.aggregate, metricMapping.dbColumn)\n\n let selectClause = `SELECT ${aggregateExpr} AS value`\n let groupByClause = ''\n let orderByClause = ''\n let limitClause = ''\n\n if (options.groupBy) {\n let groupMapping = registry.getFieldMapping(options.entityType, options.groupBy.field)\n let groupExpr: string | null = null\n\n // Handle JSONB path notation (e.g., shippingAddressSnapshot.region)\n if (!groupMapping && options.groupBy.field.includes('.')) {\n const [baseField, ...pathParts] = options.groupBy.field.split('.')\n const baseMapping = registry.getFieldMapping(options.entityType, baseField)\n if (baseMapping?.type === 'jsonb') {\n groupExpr = buildJsonbFieldExpression(baseMapping.dbColumn, pathParts.join('.'))\n }\n } else if (groupMapping) {\n if (groupMapping.type === 'timestamp' && options.groupBy.granularity) {\n groupExpr = buildDateTruncExpression(groupMapping.dbColumn, options.groupBy.granularity)\n } else {\n groupExpr = groupMapping.dbColumn\n }\n }\n\n if (groupExpr) {\n selectClause = `SELECT ${groupExpr} AS group_key, ${aggregateExpr} AS value`\n groupByClause = `GROUP BY ${groupExpr}`\n orderByClause = `ORDER BY value DESC`\n\n if (options.groupBy.limit && options.groupBy.limit > 0) {\n limitClause = `LIMIT ${Math.min(options.groupBy.limit, 100)}`\n }\n }\n }\n\n const whereClauses: string[] = []\n\n whereClauses.push(`tenant_id = ?`)\n params.push(options.scope.tenantId)\n\n if (options.scope.organizationIds && options.scope.organizationIds.length > 0) {\n whereClauses.push(`organization_id = ANY(?::uuid[])`)\n params.push(`{${options.scope.organizationIds.join(',')}}`)\n }\n\n whereClauses.push(`deleted_at IS NULL`)\n\n if (options.dateRange) {\n const dateMapping = registry.getFieldMapping(options.entityType, options.dateRange.field)\n if (dateMapping) {\n whereClauses.push(`${dateMapping.dbColumn} >= ?`)\n params.push(options.dateRange.start)\n whereClauses.push(`${dateMapping.dbColumn} <= ?`)\n params.push(options.dateRange.end)\n }\n }\n\n if (options.filters) {\n for (const filter of options.filters) {\n const filterMapping = registry.getFieldMapping(options.entityType, filter.field)\n if (!filterMapping) continue\n\n switch (filter.operator) {\n case 'eq':\n whereClauses.push(`${filterMapping.dbColumn} = ?`)\n params.push(filter.value)\n break\n case 'neq':\n whereClauses.push(`${filterMapping.dbColumn} != ?`)\n params.push(filter.value)\n break\n case 'gt':\n whereClauses.push(`${filterMapping.dbColumn} > ?`)\n params.push(filter.value)\n break\n case 'gte':\n whereClauses.push(`${filterMapping.dbColumn} >= ?`)\n params.push(filter.value)\n break\n case 'lt':\n whereClauses.push(`${filterMapping.dbColumn} < ?`)\n params.push(filter.value)\n break\n case 'lte':\n whereClauses.push(`${filterMapping.dbColumn} <= ?`)\n params.push(filter.value)\n break\n case 'in':\n whereClauses.push(`${filterMapping.dbColumn} = ANY(?)`)\n params.push(filter.value)\n break\n case 'not_in':\n whereClauses.push(`${filterMapping.dbColumn} != ALL(?)`)\n params.push(filter.value)\n break\n case 'is_null':\n whereClauses.push(`${filterMapping.dbColumn} IS NULL`)\n break\n case 'is_not_null':\n whereClauses.push(`${filterMapping.dbColumn} IS NOT NULL`)\n break\n }\n }\n }\n\n const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(' AND ')}` : ''\n\n const sql = [selectClause, `FROM ${tableName}`, whereClause, groupByClause, orderByClause, limitClause]\n .filter(Boolean)\n .join(' ')\n\n return { sql, params }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,34 @@
1
+ function formatCurrency(value, options = {}) {
2
+ const { currency = "USD", minimumFractionDigits = 0, maximumFractionDigits = 0 } = options;
3
+ return new Intl.NumberFormat(void 0, {
4
+ style: "currency",
5
+ currency,
6
+ minimumFractionDigits,
7
+ maximumFractionDigits
8
+ }).format(value);
9
+ }
10
+ function formatCurrencyWithDecimals(value, options = {}) {
11
+ return formatCurrency(value, { minimumFractionDigits: 2, maximumFractionDigits: 2, ...options });
12
+ }
13
+ function formatCurrencyCompact(value, currencySymbol = "$") {
14
+ if (Math.abs(value) >= 1e6) {
15
+ return `${currencySymbol}${(value / 1e6).toFixed(1)}M`;
16
+ }
17
+ if (Math.abs(value) >= 1e3) {
18
+ return `${currencySymbol}${(value / 1e3).toFixed(1)}K`;
19
+ }
20
+ return `${currencySymbol}${value.toFixed(0)}`;
21
+ }
22
+ function formatCurrencySafe(value, fallback = "--") {
23
+ if (value === null || value === void 0) return fallback;
24
+ const num = Number(value);
25
+ if (!Number.isFinite(num)) return fallback;
26
+ return formatCurrency(num);
27
+ }
28
+ export {
29
+ formatCurrency,
30
+ formatCurrencyCompact,
31
+ formatCurrencySafe,
32
+ formatCurrencyWithDecimals
33
+ };
34
+ //# sourceMappingURL=formatters.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/dashboards/lib/formatters.ts"],
4
+ "sourcesContent": ["export type FormatCurrencyOptions = {\n currency?: string\n minimumFractionDigits?: number\n maximumFractionDigits?: number\n}\n\nexport function formatCurrency(value: number, options: FormatCurrencyOptions = {}): string {\n const { currency = 'USD', minimumFractionDigits = 0, maximumFractionDigits = 0 } = options\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n minimumFractionDigits,\n maximumFractionDigits,\n }).format(value)\n}\n\nexport function formatCurrencyWithDecimals(value: number, options: FormatCurrencyOptions = {}): string {\n return formatCurrency(value, { minimumFractionDigits: 2, maximumFractionDigits: 2, ...options })\n}\n\nexport function formatCurrencyCompact(value: number, currencySymbol = '$'): string {\n if (Math.abs(value) >= 1_000_000) {\n return `${currencySymbol}${(value / 1_000_000).toFixed(1)}M`\n }\n if (Math.abs(value) >= 1_000) {\n return `${currencySymbol}${(value / 1_000).toFixed(1)}K`\n }\n return `${currencySymbol}${value.toFixed(0)}`\n}\n\nexport function formatCurrencySafe(value: unknown, fallback = '--'): string {\n if (value === null || value === undefined) return fallback\n const num = Number(value)\n if (!Number.isFinite(num)) return fallback\n return formatCurrency(num)\n}\n"],
5
+ "mappings": "AAMO,SAAS,eAAe,OAAe,UAAiC,CAAC,GAAW;AACzF,QAAM,EAAE,WAAW,OAAO,wBAAwB,GAAG,wBAAwB,EAAE,IAAI;AACnF,SAAO,IAAI,KAAK,aAAa,QAAW;AAAA,IACtC,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EAAE,OAAO,KAAK;AACjB;AAEO,SAAS,2BAA2B,OAAe,UAAiC,CAAC,GAAW;AACrG,SAAO,eAAe,OAAO,EAAE,uBAAuB,GAAG,uBAAuB,GAAG,GAAG,QAAQ,CAAC;AACjG;AAEO,SAAS,sBAAsB,OAAe,iBAAiB,KAAa;AACjF,MAAI,KAAK,IAAI,KAAK,KAAK,KAAW;AAChC,WAAO,GAAG,cAAc,IAAI,QAAQ,KAAW,QAAQ,CAAC,CAAC;AAAA,EAC3D;AACA,MAAI,KAAK,IAAI,KAAK,KAAK,KAAO;AAC5B,WAAO,GAAG,cAAc,IAAI,QAAQ,KAAO,QAAQ,CAAC,CAAC;AAAA,EACvD;AACA,SAAO,GAAG,cAAc,GAAG,MAAM,QAAQ,CAAC,CAAC;AAC7C;AAEO,SAAS,mBAAmB,OAAgB,WAAW,MAAc;AAC1E,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,QAAM,MAAM,OAAO,KAAK;AACxB,MAAI,CAAC,OAAO,SAAS,GAAG,EAAG,QAAO;AAClC,SAAO,eAAe,GAAG;AAC3B;",
6
+ "names": []
7
+ }