@resolveio/server-lib 22.3.220 → 22.3.221

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 (745) hide show
  1. package/.nodemon.json +5 -0
  2. package/.vscode/settings.json +21 -0
  3. package/AGENTS.md +195 -0
  4. package/README.md +22 -0
  5. package/build_package.sh +5 -0
  6. package/compileDTS.pl +64 -0
  7. package/docs/ai-assistant-nightly-eval.md +65 -0
  8. package/docs/ai-assistant-preflight-checklist.md +23 -0
  9. package/docs/ai-assistant-report-builder-bridge-playbook.md +115 -0
  10. package/eslint-plugin-custom/index.js +7 -0
  11. package/eslint-plugin-custom/rules/no-filter-zero-index.js +44 -0
  12. package/eslint.config.js +103 -0
  13. package/gulpfile.js +216 -0
  14. package/methodAndPublicationListGenerator.py +375 -0
  15. package/mongodbensurers.js +2 -0
  16. package/mongostop.js +3 -0
  17. package/package.json +1 -1
  18. package/scripts/cleanup-bypassed-callmethod-logs.js +616 -0
  19. package/settings.development.json +25 -0
  20. package/settings.development.redacted.json +25 -0
  21. package/src/.env +12 -0
  22. package/src/ai/assistant-core-heuristics.ts +379 -0
  23. package/src/ai/resolveio-platform-intelligence-memory-corpus.ts +185 -0
  24. package/src/ai/resolveio-platform-intelligence-memory.ts +325 -0
  25. package/{ai/resolveio-platform-intelligence-types.d.ts → src/ai/resolveio-platform-intelligence-types.ts} +20 -15
  26. package/src/ai/resolveio-platform-intelligence.ts +462 -0
  27. package/src/client-server-app.ts +12 -0
  28. package/src/collections/ai-run.collection.ts +117 -0
  29. package/src/collections/ai-terminal-conversation.collection.ts +91 -0
  30. package/src/collections/ai-terminal-issue-report.collection.ts +99 -0
  31. package/src/collections/ai-terminal-message.collection.ts +77 -0
  32. package/src/collections/app-setting.collection.ts +104 -0
  33. package/src/collections/app-status.collection.ts +58 -0
  34. package/src/collections/communication-metric.collection.ts +84 -0
  35. package/src/collections/counter.collection.ts +56 -0
  36. package/src/collections/cron-job-history.collection.ts +94 -0
  37. package/src/collections/cron-job.collection.ts +92 -0
  38. package/src/collections/customer-notification.collection.ts +131 -0
  39. package/src/collections/customer-portal-password.collection.ts +76 -0
  40. package/src/collections/email-history.collection.ts +134 -0
  41. package/src/collections/email-verified.collection.ts +62 -0
  42. package/src/collections/file.collection.ts +74 -0
  43. package/src/collections/flag-update.collection.ts +57 -0
  44. package/src/collections/flag.collection.ts +57 -0
  45. package/src/collections/log-method-latency.collection.ts +77 -0
  46. package/src/collections/log-subscription.collection.ts +80 -0
  47. package/src/collections/log.collection.ts +93 -0
  48. package/src/collections/logged-in-users.collection.ts +67 -0
  49. package/src/collections/monitor-cpu.collection.ts +65 -0
  50. package/src/collections/monitor-function.collection.ts +74 -0
  51. package/src/collections/monitor-memory.collection.ts +77 -0
  52. package/src/collections/monitor-mongo.collection.ts +71 -0
  53. package/src/collections/notification.collection.ts +57 -0
  54. package/src/collections/openai-usage-ledger.collection.ts +131 -0
  55. package/src/collections/report-builder-dashboard-builder.collection.ts +109 -0
  56. package/src/collections/report-builder-library.collection.ts +89 -0
  57. package/src/collections/report-builder-report.collection.ts +184 -0
  58. package/src/collections/user-group.collection.ts +89 -0
  59. package/src/collections/user-guide.collection.ts +57 -0
  60. package/src/collections/user.collection.ts +181 -0
  61. package/src/cron/cron.ts +117 -0
  62. package/src/fixtures/cron-jobs.ts +95 -0
  63. package/src/fixtures/init.ts +35 -0
  64. package/src/http/auth.ts +818 -0
  65. package/src/http/health.ts +7 -0
  66. package/src/http/home.ts +90 -0
  67. package/src/http/slow-query-publication.ts +49 -0
  68. package/src/index.ts +1 -0
  69. package/src/managers/ai-assistant-codex-manager.manager.ts +1131 -0
  70. package/src/managers/ai-run-evidence.manager.ts +264 -0
  71. package/src/managers/communication-metric.manager.ts +82 -0
  72. package/src/managers/cron.manager.ts +333 -0
  73. package/src/managers/customer-notification-content.manager.ts +236 -0
  74. package/src/managers/diagnostic-manager-bootstrap.ts +165 -0
  75. package/src/managers/error-auto-fix.manager.ts +2767 -0
  76. package/src/managers/local-log.manager.ts +113 -0
  77. package/src/managers/method.manager.ts +1857 -0
  78. package/src/managers/mongo.manager.ts +4575 -0
  79. package/src/managers/monitor.manager.ts +507 -0
  80. package/src/managers/openai-usage-ledger.manager.ts +112 -0
  81. package/src/managers/slow-query-verifier.manager.ts +3590 -0
  82. package/src/managers/slow-query.manager.ts +519 -0
  83. package/src/managers/subscription.manager.ts +3128 -0
  84. package/src/managers/websocket.manager.ts +746 -0
  85. package/src/managers/worker-dispatcher.manager.ts +1360 -0
  86. package/src/managers/worker-server.manager.ts +536 -0
  87. package/src/methods/accounts.ts +532 -0
  88. package/src/methods/ai-terminal.ts +29070 -0
  89. package/src/methods/app-settings.ts +114 -0
  90. package/src/methods/aws.ts +649 -0
  91. package/src/methods/collections.ts +641 -0
  92. package/src/methods/counters.ts +69 -0
  93. package/src/methods/cron-jobs.ts +2614 -0
  94. package/src/methods/customer-notifications.ts +458 -0
  95. package/src/methods/diagnostics.ts +616 -0
  96. package/src/methods/flag-updates.ts +7 -0
  97. package/src/methods/flags.ts +7 -0
  98. package/src/methods/logs.ts +657 -0
  99. package/src/methods/mongo-explorer.ts +1880 -0
  100. package/src/methods/monitor.ts +540 -0
  101. package/src/methods/pdf.ts +1236 -0
  102. package/src/methods/publications.ts +129 -0
  103. package/src/methods/report-builder.ts +3300 -0
  104. package/src/methods/support.ts +335 -0
  105. package/src/models/ai-run.model.ts +27 -0
  106. package/src/models/ai-terminal-conversation.model.ts +19 -0
  107. package/src/models/ai-terminal-issue-report.model.ts +21 -0
  108. package/src/models/ai-terminal-message.model.ts +24 -0
  109. package/src/models/app-setting.model.ts +17 -0
  110. package/{models/app-status.model.d.ts → src/models/app-status.model.ts} +3 -2
  111. package/{models/billing-logged-in-users.model.d.ts → src/models/billing-logged-in-users.model.ts} +5 -4
  112. package/src/models/collection-document.model.ts +24 -0
  113. package/src/models/communication-metric.model.ts +23 -0
  114. package/{models/counter.model.d.ts → src/models/counter.model.ts} +4 -3
  115. package/src/models/cron-job-history.model.ts +16 -0
  116. package/src/models/cron-job.model.ts +15 -0
  117. package/src/models/customer-notification.model.ts +28 -0
  118. package/src/models/customer-portal-password.model.ts +12 -0
  119. package/src/models/dialog.model.ts +25 -0
  120. package/{models/email-history.model.js → src/models/email-history.model.ts} +36 -4
  121. package/{models/email-verified.model.d.ts → src/models/email-verified.model.ts} +6 -5
  122. package/{models/file.model.d.ts → src/models/file.model.ts} +8 -7
  123. package/{models/flag-update.model.d.ts → src/models/flag-update.model.ts} +4 -3
  124. package/{models/flag.model.d.ts → src/models/flag.model.ts} +4 -3
  125. package/src/models/log-method-latency.model.ts +11 -0
  126. package/{models/log-subscription.model.d.ts → src/models/log-subscription.model.ts} +11 -9
  127. package/src/models/log.model.ts +19 -0
  128. package/{models/logged-in-users.model.d.ts → src/models/logged-in-users.model.ts} +6 -5
  129. package/{models/method-response.model.d.ts → src/models/method-response.model.ts} +7 -6
  130. package/src/models/method.model.ts +25 -0
  131. package/{models/monitor-cpu.model.d.ts → src/models/monitor-cpu.model.ts} +9 -7
  132. package/src/models/monitor-function.model.ts +16 -0
  133. package/src/models/monitor-memory.model.ts +17 -0
  134. package/src/models/monitor-mongo.model.ts +15 -0
  135. package/{models/notification.model.d.ts → src/models/notification.model.ts} +6 -4
  136. package/src/models/openai-usage-ledger.model.ts +56 -0
  137. package/src/models/pagination.model.ts +35 -0
  138. package/src/models/permission.model.ts +14 -0
  139. package/src/models/report-builder-dashboard-builder.model.ts +29 -0
  140. package/src/models/report-builder-library.model.ts +20 -0
  141. package/src/models/report-builder-report.model.ts +136 -0
  142. package/src/models/report-builder.model.ts +68 -0
  143. package/src/models/select-data-label.model.ts +9 -0
  144. package/src/models/server-message.model.ts +31 -0
  145. package/src/models/slow-query-report.model.ts +23 -0
  146. package/src/models/subscription.model.ts +73 -0
  147. package/src/models/support-ticket.model.ts +104 -0
  148. package/src/models/user-group.model.ts +24 -0
  149. package/{models/user-guide.model.d.ts → src/models/user-guide.model.ts} +5 -4
  150. package/src/models/user.model.ts +96 -0
  151. package/src/private/images/ResolveIO.png +0 -0
  152. package/src/publications/ai-terminal.ts +73 -0
  153. package/src/publications/app-settings.ts +25 -0
  154. package/src/publications/app-status.ts +13 -0
  155. package/src/publications/cron-jobs.ts +40 -0
  156. package/src/publications/customer-notifications.ts +101 -0
  157. package/src/publications/files.ts +33 -0
  158. package/src/publications/flags-update.ts +19 -0
  159. package/src/publications/flags.ts +19 -0
  160. package/src/publications/logs.ts +163 -0
  161. package/src/publications/notifications.ts +13 -0
  162. package/src/publications/report-builder-dashboard-builders.ts +39 -0
  163. package/src/publications/report-builder-libraries.ts +41 -0
  164. package/src/publications/report-builder-reports.ts +47 -0
  165. package/src/publications/super-admin.ts +13 -0
  166. package/src/publications/user-groups.ts +12 -0
  167. package/src/publications/user-guides.ts +12 -0
  168. package/src/resolveio-server-app.ts +617 -0
  169. package/src/server-app.ts +3354 -0
  170. package/src/services/codex-client.ts +1231 -0
  171. package/src/services/openai-client.ts +265 -0
  172. package/src/types/error-report.ts +26 -0
  173. package/src/types/js-tiktoken.d.ts +11 -0
  174. package/src/types/slow-query-report.ts +28 -0
  175. package/src/util/ai-qa-policy.ts +925 -0
  176. package/src/util/ai-run-evidence-adapters.ts +8347 -0
  177. package/src/util/ai-run-evidence-dashboard.ts +323 -0
  178. package/src/util/ai-run-evidence-eval.ts +1057 -0
  179. package/src/util/ai-run-evidence.ts +1430 -0
  180. package/src/util/ai-runner-artifacts.ts +586 -0
  181. package/src/util/ai-runner-manager-autopilot.ts +961 -0
  182. package/src/util/ai-runner-manager-policy.ts +5011 -0
  183. package/src/util/ai-runner-qa-auth.ts +838 -0
  184. package/src/util/ai-runner-qa-tools.ts +3536 -0
  185. package/src/util/aicoder-runner-v6.ts +3121 -0
  186. package/src/util/common.ts +649 -0
  187. package/src/util/customer-portal-password.ts +183 -0
  188. package/src/util/error-reporter.ts +332 -0
  189. package/src/util/error-tracking.ts +79 -0
  190. package/src/util/openai-usage-cost.ts +114 -0
  191. package/src/util/report-builder-unwinds.ts +180 -0
  192. package/src/util/runner-process-janitor.ts +219 -0
  193. package/src/util/schema-report-builder.ts +448 -0
  194. package/src/util/slow-query-reporter.ts +216 -0
  195. package/src/util/subscription-dependency-context.ts +1096 -0
  196. package/src/util/support-runner-v5.ts +10040 -0
  197. package/src/util/tokenizer.ts +38 -0
  198. package/src/workers/codex-runner.worker.ts +142 -0
  199. package/start_server.sh +5 -0
  200. package/tests/ai-assistant-corpus-build.ts +484 -0
  201. package/tests/ai-assistant-corpus-replay-e2e.ts +774 -0
  202. package/tests/ai-assistant-data-parity-e2e.ts +1989 -0
  203. package/tests/ai-assistant-eval-triage.ts +831 -0
  204. package/tests/ai-assistant-openai-e2e.ts +1061 -0
  205. package/tests/ai-assistant-openai-git-e2e.ts +155 -0
  206. package/tests/ai-assistant-preflight-matrix.ts +215 -0
  207. package/tests/ai-assistant-routing-eval.test.ts +585 -0
  208. package/tests/ai-assistant-snf-live-eval.ts +975 -0
  209. package/tests/ai-assistant-utils.test.ts +4834 -0
  210. package/tests/ai-manager-autopilot-snapshot.test.ts +193 -0
  211. package/tests/ai-manager-recovery-checkpoint.test.ts +1383 -0
  212. package/tests/ai-run-eval.test.ts +132 -0
  213. package/tests/ai-run-evidence.test.ts +3773 -0
  214. package/tests/ai-runner-contract.test.ts +515 -0
  215. package/tests/aicoder-runner-v6.test.ts +822 -0
  216. package/tests/error-reporter.test.ts +145 -0
  217. package/tests/method-publication-generator.test.ts +46 -0
  218. package/tests/report-builder-linking.test.ts +79 -0
  219. package/tests/resolveio-platform-intelligence.test.ts +352 -0
  220. package/tests/server-app-cron-owner.test.ts +127 -0
  221. package/tests/subscription-connect-race.test.ts +158 -0
  222. package/tests/subscription-dependency-context.test.ts +324 -0
  223. package/tests/subscription-manager-collection-tracking.test.ts +86 -0
  224. package/tests/subscription-manager-invalidation.test.ts +86 -0
  225. package/tests/support-runner-v5.test.ts +3201 -0
  226. package/tsconfig.json +34 -0
  227. package/ai/assistant-core-heuristics.d.ts +0 -11
  228. package/ai/assistant-core-heuristics.js +0 -356
  229. package/ai/assistant-core-heuristics.js.map +0 -1
  230. package/ai/resolveio-platform-intelligence-memory-corpus.d.ts +0 -3
  231. package/ai/resolveio-platform-intelligence-memory-corpus.js +0 -214
  232. package/ai/resolveio-platform-intelligence-memory-corpus.js.map +0 -1
  233. package/ai/resolveio-platform-intelligence-memory.d.ts +0 -20
  234. package/ai/resolveio-platform-intelligence-memory.js +0 -341
  235. package/ai/resolveio-platform-intelligence-memory.js.map +0 -1
  236. package/ai/resolveio-platform-intelligence-types.js +0 -4
  237. package/ai/resolveio-platform-intelligence-types.js.map +0 -1
  238. package/ai/resolveio-platform-intelligence.d.ts +0 -6
  239. package/ai/resolveio-platform-intelligence.js +0 -463
  240. package/ai/resolveio-platform-intelligence.js.map +0 -1
  241. package/client-server-app.d.ts +0 -1
  242. package/client-server-app.js +0 -68
  243. package/client-server-app.js.map +0 -1
  244. package/collections/ai-run.collection.d.ts +0 -3
  245. package/collections/ai-run.collection.js +0 -170
  246. package/collections/ai-run.collection.js.map +0 -1
  247. package/collections/ai-terminal-conversation.collection.d.ts +0 -2
  248. package/collections/ai-terminal-conversation.collection.js +0 -140
  249. package/collections/ai-terminal-conversation.collection.js.map +0 -1
  250. package/collections/ai-terminal-issue-report.collection.d.ts +0 -2
  251. package/collections/ai-terminal-issue-report.collection.js +0 -148
  252. package/collections/ai-terminal-issue-report.collection.js.map +0 -1
  253. package/collections/ai-terminal-message.collection.d.ts +0 -2
  254. package/collections/ai-terminal-message.collection.js +0 -121
  255. package/collections/ai-terminal-message.collection.js.map +0 -1
  256. package/collections/app-setting.collection.d.ts +0 -3
  257. package/collections/app-setting.collection.js +0 -103
  258. package/collections/app-setting.collection.js.map +0 -1
  259. package/collections/app-status.collection.d.ts +0 -3
  260. package/collections/app-status.collection.js +0 -57
  261. package/collections/app-status.collection.js.map +0 -1
  262. package/collections/communication-metric.collection.d.ts +0 -2
  263. package/collections/communication-metric.collection.js +0 -133
  264. package/collections/communication-metric.collection.js.map +0 -1
  265. package/collections/counter.collection.d.ts +0 -3
  266. package/collections/counter.collection.js +0 -56
  267. package/collections/counter.collection.js.map +0 -1
  268. package/collections/cron-job-history.collection.d.ts +0 -3
  269. package/collections/cron-job-history.collection.js +0 -137
  270. package/collections/cron-job-history.collection.js.map +0 -1
  271. package/collections/cron-job.collection.d.ts +0 -3
  272. package/collections/cron-job.collection.js +0 -92
  273. package/collections/cron-job.collection.js.map +0 -1
  274. package/collections/customer-notification.collection.d.ts +0 -3
  275. package/collections/customer-notification.collection.js +0 -130
  276. package/collections/customer-notification.collection.js.map +0 -1
  277. package/collections/customer-portal-password.collection.d.ts +0 -3
  278. package/collections/customer-portal-password.collection.js +0 -75
  279. package/collections/customer-portal-password.collection.js.map +0 -1
  280. package/collections/email-history.collection.d.ts +0 -3
  281. package/collections/email-history.collection.js +0 -134
  282. package/collections/email-history.collection.js.map +0 -1
  283. package/collections/email-verified.collection.d.ts +0 -3
  284. package/collections/email-verified.collection.js +0 -62
  285. package/collections/email-verified.collection.js.map +0 -1
  286. package/collections/file.collection.d.ts +0 -3
  287. package/collections/file.collection.js +0 -74
  288. package/collections/file.collection.js.map +0 -1
  289. package/collections/flag-update.collection.d.ts +0 -3
  290. package/collections/flag-update.collection.js +0 -57
  291. package/collections/flag-update.collection.js.map +0 -1
  292. package/collections/flag.collection.d.ts +0 -3
  293. package/collections/flag.collection.js +0 -57
  294. package/collections/flag.collection.js.map +0 -1
  295. package/collections/log-method-latency.collection.d.ts +0 -3
  296. package/collections/log-method-latency.collection.js +0 -77
  297. package/collections/log-method-latency.collection.js.map +0 -1
  298. package/collections/log-subscription.collection.d.ts +0 -3
  299. package/collections/log-subscription.collection.js +0 -80
  300. package/collections/log-subscription.collection.js.map +0 -1
  301. package/collections/log.collection.d.ts +0 -3
  302. package/collections/log.collection.js +0 -93
  303. package/collections/log.collection.js.map +0 -1
  304. package/collections/logged-in-users.collection.d.ts +0 -3
  305. package/collections/logged-in-users.collection.js +0 -67
  306. package/collections/logged-in-users.collection.js.map +0 -1
  307. package/collections/monitor-cpu.collection.d.ts +0 -3
  308. package/collections/monitor-cpu.collection.js +0 -65
  309. package/collections/monitor-cpu.collection.js.map +0 -1
  310. package/collections/monitor-function.collection.d.ts +0 -3
  311. package/collections/monitor-function.collection.js +0 -74
  312. package/collections/monitor-function.collection.js.map +0 -1
  313. package/collections/monitor-memory.collection.d.ts +0 -3
  314. package/collections/monitor-memory.collection.js +0 -77
  315. package/collections/monitor-memory.collection.js.map +0 -1
  316. package/collections/monitor-mongo.collection.d.ts +0 -3
  317. package/collections/monitor-mongo.collection.js +0 -71
  318. package/collections/monitor-mongo.collection.js.map +0 -1
  319. package/collections/notification.collection.d.ts +0 -3
  320. package/collections/notification.collection.js +0 -57
  321. package/collections/notification.collection.js.map +0 -1
  322. package/collections/openai-usage-ledger.collection.d.ts +0 -2
  323. package/collections/openai-usage-ledger.collection.js +0 -188
  324. package/collections/openai-usage-ledger.collection.js.map +0 -1
  325. package/collections/report-builder-dashboard-builder.collection.d.ts +0 -3
  326. package/collections/report-builder-dashboard-builder.collection.js +0 -109
  327. package/collections/report-builder-dashboard-builder.collection.js.map +0 -1
  328. package/collections/report-builder-library.collection.d.ts +0 -3
  329. package/collections/report-builder-library.collection.js +0 -87
  330. package/collections/report-builder-library.collection.js.map +0 -1
  331. package/collections/report-builder-report.collection.d.ts +0 -4
  332. package/collections/report-builder-report.collection.js +0 -184
  333. package/collections/report-builder-report.collection.js.map +0 -1
  334. package/collections/user-group.collection.d.ts +0 -4
  335. package/collections/user-group.collection.js +0 -89
  336. package/collections/user-group.collection.js.map +0 -1
  337. package/collections/user-guide.collection.d.ts +0 -3
  338. package/collections/user-guide.collection.js +0 -57
  339. package/collections/user-guide.collection.js.map +0 -1
  340. package/collections/user.collection.d.ts +0 -4
  341. package/collections/user.collection.js +0 -180
  342. package/collections/user.collection.js.map +0 -1
  343. package/cron/cron.d.ts +0 -14
  344. package/cron/cron.js +0 -216
  345. package/cron/cron.js.map +0 -1
  346. package/fixtures/cron-jobs.d.ts +0 -1
  347. package/fixtures/cron-jobs.js +0 -150
  348. package/fixtures/cron-jobs.js.map +0 -1
  349. package/fixtures/init.d.ts +0 -1
  350. package/fixtures/init.js +0 -91
  351. package/fixtures/init.js.map +0 -1
  352. package/http/auth.d.ts +0 -2
  353. package/http/auth.js +0 -951
  354. package/http/auth.js.map +0 -1
  355. package/http/health.d.ts +0 -1
  356. package/http/health.js +0 -11
  357. package/http/health.js.map +0 -1
  358. package/http/home.d.ts +0 -1
  359. package/http/home.js +0 -134
  360. package/http/home.js.map +0 -1
  361. package/http/slow-query-publication.d.ts +0 -2
  362. package/http/slow-query-publication.js +0 -99
  363. package/http/slow-query-publication.js.map +0 -1
  364. package/index.d.ts +0 -1
  365. package/index.js +0 -19
  366. package/index.js.map +0 -1
  367. package/managers/ai-assistant-codex-manager.manager.d.ts +0 -67
  368. package/managers/ai-assistant-codex-manager.manager.js +0 -1113
  369. package/managers/ai-assistant-codex-manager.manager.js.map +0 -1
  370. package/managers/ai-run-evidence.manager.d.ts +0 -36
  371. package/managers/ai-run-evidence.manager.js +0 -377
  372. package/managers/ai-run-evidence.manager.js.map +0 -1
  373. package/managers/communication-metric.manager.d.ts +0 -16
  374. package/managers/communication-metric.manager.js +0 -134
  375. package/managers/communication-metric.manager.js.map +0 -1
  376. package/managers/cron.manager.d.ts +0 -20
  377. package/managers/cron.manager.js +0 -534
  378. package/managers/cron.manager.js.map +0 -1
  379. package/managers/customer-notification-content.manager.d.ts +0 -55
  380. package/managers/customer-notification-content.manager.js +0 -158
  381. package/managers/customer-notification-content.manager.js.map +0 -1
  382. package/managers/diagnostic-manager-bootstrap.d.ts +0 -9
  383. package/managers/diagnostic-manager-bootstrap.js +0 -260
  384. package/managers/diagnostic-manager-bootstrap.js.map +0 -1
  385. package/managers/error-auto-fix.manager.d.ts +0 -149
  386. package/managers/error-auto-fix.manager.js +0 -3064
  387. package/managers/error-auto-fix.manager.js.map +0 -1
  388. package/managers/local-log.manager.d.ts +0 -18
  389. package/managers/local-log.manager.js +0 -88
  390. package/managers/local-log.manager.js.map +0 -1
  391. package/managers/method.manager.d.ts +0 -84
  392. package/managers/method.manager.js +0 -1964
  393. package/managers/method.manager.js.map +0 -1
  394. package/managers/mongo.manager.d.ts +0 -224
  395. package/managers/mongo.manager.js +0 -5000
  396. package/managers/mongo.manager.js.map +0 -1
  397. package/managers/monitor.manager.d.ts +0 -70
  398. package/managers/monitor.manager.js +0 -550
  399. package/managers/monitor.manager.js.map +0 -1
  400. package/managers/openai-usage-ledger.manager.d.ts +0 -30
  401. package/managers/openai-usage-ledger.manager.js +0 -142
  402. package/managers/openai-usage-ledger.manager.js.map +0 -1
  403. package/managers/slow-query-verifier.manager.d.ts +0 -144
  404. package/managers/slow-query-verifier.manager.js +0 -3857
  405. package/managers/slow-query-verifier.manager.js.map +0 -1
  406. package/managers/slow-query.manager.d.ts +0 -28
  407. package/managers/slow-query.manager.js +0 -468
  408. package/managers/slow-query.manager.js.map +0 -1
  409. package/managers/subscription.manager.d.ts +0 -169
  410. package/managers/subscription.manager.js +0 -3434
  411. package/managers/subscription.manager.js.map +0 -1
  412. package/managers/websocket.manager.d.ts +0 -73
  413. package/managers/websocket.manager.js +0 -673
  414. package/managers/websocket.manager.js.map +0 -1
  415. package/managers/worker-dispatcher.manager.d.ts +0 -120
  416. package/managers/worker-dispatcher.manager.js +0 -1266
  417. package/managers/worker-dispatcher.manager.js.map +0 -1
  418. package/managers/worker-server.manager.d.ts +0 -35
  419. package/managers/worker-server.manager.js +0 -582
  420. package/managers/worker-server.manager.js.map +0 -1
  421. package/methods/accounts.d.ts +0 -2
  422. package/methods/accounts.js +0 -624
  423. package/methods/accounts.js.map +0 -1
  424. package/methods/ai-terminal.d.ts +0 -458
  425. package/methods/ai-terminal.js +0 -27991
  426. package/methods/ai-terminal.js.map +0 -1
  427. package/methods/app-settings.d.ts +0 -2
  428. package/methods/app-settings.js +0 -169
  429. package/methods/app-settings.js.map +0 -1
  430. package/methods/aws.d.ts +0 -2
  431. package/methods/aws.js +0 -877
  432. package/methods/aws.js.map +0 -1
  433. package/methods/collections.d.ts +0 -2
  434. package/methods/collections.js +0 -719
  435. package/methods/collections.js.map +0 -1
  436. package/methods/counters.d.ts +0 -2
  437. package/methods/counters.js +0 -113
  438. package/methods/counters.js.map +0 -1
  439. package/methods/cron-jobs.d.ts +0 -2
  440. package/methods/cron-jobs.js +0 -2475
  441. package/methods/cron-jobs.js.map +0 -1
  442. package/methods/customer-notifications.d.ts +0 -2
  443. package/methods/customer-notifications.js +0 -528
  444. package/methods/customer-notifications.js.map +0 -1
  445. package/methods/diagnostics.d.ts +0 -2
  446. package/methods/diagnostics.js +0 -703
  447. package/methods/diagnostics.js.map +0 -1
  448. package/methods/flag-updates.d.ts +0 -2
  449. package/methods/flag-updates.js +0 -8
  450. package/methods/flag-updates.js.map +0 -1
  451. package/methods/flags.d.ts +0 -2
  452. package/methods/flags.js +0 -8
  453. package/methods/flags.js.map +0 -1
  454. package/methods/logs.d.ts +0 -2
  455. package/methods/logs.js +0 -751
  456. package/methods/logs.js.map +0 -1
  457. package/methods/mongo-explorer.d.ts +0 -2
  458. package/methods/mongo-explorer.js +0 -1808
  459. package/methods/mongo-explorer.js.map +0 -1
  460. package/methods/monitor.d.ts +0 -2
  461. package/methods/monitor.js +0 -543
  462. package/methods/monitor.js.map +0 -1
  463. package/methods/pdf.d.ts +0 -2
  464. package/methods/pdf.js +0 -1216
  465. package/methods/pdf.js.map +0 -1
  466. package/methods/publications.d.ts +0 -1
  467. package/methods/publications.js +0 -183
  468. package/methods/publications.js.map +0 -1
  469. package/methods/report-builder.d.ts +0 -2
  470. package/methods/report-builder.js +0 -3094
  471. package/methods/report-builder.js.map +0 -1
  472. package/methods/support.d.ts +0 -2
  473. package/methods/support.js +0 -430
  474. package/methods/support.js.map +0 -1
  475. package/models/ai-run.model.d.ts +0 -19
  476. package/models/ai-run.model.js +0 -4
  477. package/models/ai-run.model.js.map +0 -1
  478. package/models/ai-terminal-conversation.model.d.ts +0 -17
  479. package/models/ai-terminal-conversation.model.js +0 -4
  480. package/models/ai-terminal-conversation.model.js.map +0 -1
  481. package/models/ai-terminal-issue-report.model.d.ts +0 -19
  482. package/models/ai-terminal-issue-report.model.js +0 -4
  483. package/models/ai-terminal-issue-report.model.js.map +0 -1
  484. package/models/ai-terminal-message.model.d.ts +0 -22
  485. package/models/ai-terminal-message.model.js +0 -4
  486. package/models/ai-terminal-message.model.js.map +0 -1
  487. package/models/app-setting.model.d.ts +0 -16
  488. package/models/app-setting.model.js +0 -4
  489. package/models/app-setting.model.js.map +0 -1
  490. package/models/app-status.model.js +0 -4
  491. package/models/app-status.model.js.map +0 -1
  492. package/models/billing-logged-in-users.model.js +0 -4
  493. package/models/billing-logged-in-users.model.js.map +0 -1
  494. package/models/collection-document.model.d.ts +0 -21
  495. package/models/collection-document.model.js +0 -4
  496. package/models/collection-document.model.js.map +0 -1
  497. package/models/communication-metric.model.d.ts +0 -20
  498. package/models/communication-metric.model.js +0 -4
  499. package/models/communication-metric.model.js.map +0 -1
  500. package/models/counter.model.js +0 -4
  501. package/models/counter.model.js.map +0 -1
  502. package/models/cron-job-history.model.d.ts +0 -15
  503. package/models/cron-job-history.model.js +0 -4
  504. package/models/cron-job-history.model.js.map +0 -1
  505. package/models/cron-job.model.d.ts +0 -14
  506. package/models/cron-job.model.js +0 -4
  507. package/models/cron-job.model.js.map +0 -1
  508. package/models/customer-notification.model.d.ts +0 -26
  509. package/models/customer-notification.model.js +0 -4
  510. package/models/customer-notification.model.js.map +0 -1
  511. package/models/customer-portal-password.model.d.ts +0 -11
  512. package/models/customer-portal-password.model.js +0 -4
  513. package/models/customer-portal-password.model.js.map +0 -1
  514. package/models/dialog.model.d.ts +0 -23
  515. package/models/dialog.model.js +0 -4
  516. package/models/dialog.model.js.map +0 -1
  517. package/models/email-history.model.d.ts +0 -32
  518. package/models/email-history.model.js.map +0 -1
  519. package/models/email-verified.model.js +0 -4
  520. package/models/email-verified.model.js.map +0 -1
  521. package/models/file.model.js +0 -4
  522. package/models/file.model.js.map +0 -1
  523. package/models/flag-update.model.js +0 -4
  524. package/models/flag-update.model.js.map +0 -1
  525. package/models/flag.model.js +0 -4
  526. package/models/flag.model.js.map +0 -1
  527. package/models/log-method-latency.model.d.ts +0 -10
  528. package/models/log-method-latency.model.js +0 -4
  529. package/models/log-method-latency.model.js.map +0 -1
  530. package/models/log-subscription.model.js +0 -4
  531. package/models/log-subscription.model.js.map +0 -1
  532. package/models/log.model.d.ts +0 -17
  533. package/models/log.model.js +0 -4
  534. package/models/log.model.js.map +0 -1
  535. package/models/logged-in-users.model.js +0 -4
  536. package/models/logged-in-users.model.js.map +0 -1
  537. package/models/method-response.model.js +0 -4
  538. package/models/method-response.model.js.map +0 -1
  539. package/models/method.model.d.ts +0 -26
  540. package/models/method.model.js +0 -4
  541. package/models/method.model.js.map +0 -1
  542. package/models/monitor-cpu.model.js +0 -4
  543. package/models/monitor-cpu.model.js.map +0 -1
  544. package/models/monitor-function.model.d.ts +0 -14
  545. package/models/monitor-function.model.js +0 -4
  546. package/models/monitor-function.model.js.map +0 -1
  547. package/models/monitor-memory.model.d.ts +0 -15
  548. package/models/monitor-memory.model.js +0 -4
  549. package/models/monitor-memory.model.js.map +0 -1
  550. package/models/monitor-mongo.model.d.ts +0 -13
  551. package/models/monitor-mongo.model.js +0 -4
  552. package/models/monitor-mongo.model.js.map +0 -1
  553. package/models/notification.model.js +0 -4
  554. package/models/notification.model.js.map +0 -1
  555. package/models/openai-usage-ledger.model.d.ts +0 -30
  556. package/models/openai-usage-ledger.model.js +0 -4
  557. package/models/openai-usage-ledger.model.js.map +0 -1
  558. package/models/pagination.model.d.ts +0 -11
  559. package/models/pagination.model.js +0 -28
  560. package/models/pagination.model.js.map +0 -1
  561. package/models/permission.model.d.ts +0 -12
  562. package/models/permission.model.js +0 -4
  563. package/models/permission.model.js.map +0 -1
  564. package/models/report-builder-dashboard-builder.model.d.ts +0 -25
  565. package/models/report-builder-dashboard-builder.model.js +0 -4
  566. package/models/report-builder-dashboard-builder.model.js.map +0 -1
  567. package/models/report-builder-library.model.d.ts +0 -17
  568. package/models/report-builder-library.model.js +0 -4
  569. package/models/report-builder-library.model.js.map +0 -1
  570. package/models/report-builder-report.model.d.ts +0 -121
  571. package/models/report-builder-report.model.js +0 -4
  572. package/models/report-builder-report.model.js.map +0 -1
  573. package/models/report-builder.model.d.ts +0 -61
  574. package/models/report-builder.model.js +0 -4
  575. package/models/report-builder.model.js.map +0 -1
  576. package/models/select-data-label.model.d.ts +0 -9
  577. package/models/select-data-label.model.js +0 -4
  578. package/models/select-data-label.model.js.map +0 -1
  579. package/models/server-message.model.d.ts +0 -32
  580. package/models/server-message.model.js +0 -4
  581. package/models/server-message.model.js.map +0 -1
  582. package/models/slow-query-report.model.d.ts +0 -23
  583. package/models/slow-query-report.model.js +0 -4
  584. package/models/slow-query-report.model.js.map +0 -1
  585. package/models/subscription.model.d.ts +0 -31
  586. package/models/subscription.model.js +0 -4
  587. package/models/subscription.model.js.map +0 -1
  588. package/models/support-ticket.model.d.ts +0 -87
  589. package/models/support-ticket.model.js +0 -4
  590. package/models/support-ticket.model.js.map +0 -1
  591. package/models/user-group.model.d.ts +0 -20
  592. package/models/user-group.model.js +0 -4
  593. package/models/user-group.model.js.map +0 -1
  594. package/models/user-guide.model.js +0 -4
  595. package/models/user-guide.model.js.map +0 -1
  596. package/models/user.model.d.ts +0 -84
  597. package/models/user.model.js +0 -4
  598. package/models/user.model.js.map +0 -1
  599. package/private/images/ResolveIO.png +0 -0
  600. package/public_api.js +0 -127
  601. package/public_api.js.map +0 -1
  602. package/publications/ai-terminal.d.ts +0 -1
  603. package/publications/ai-terminal.js +0 -122
  604. package/publications/ai-terminal.js.map +0 -1
  605. package/publications/app-settings.d.ts +0 -2
  606. package/publications/app-settings.js +0 -28
  607. package/publications/app-settings.js.map +0 -1
  608. package/publications/app-status.d.ts +0 -2
  609. package/publications/app-status.js +0 -16
  610. package/publications/app-status.js.map +0 -1
  611. package/publications/cron-jobs.d.ts +0 -2
  612. package/publications/cron-jobs.js +0 -88
  613. package/publications/cron-jobs.js.map +0 -1
  614. package/publications/customer-notifications.d.ts +0 -2
  615. package/publications/customer-notifications.js +0 -161
  616. package/publications/customer-notifications.js.map +0 -1
  617. package/publications/files.d.ts +0 -2
  618. package/publications/files.js +0 -36
  619. package/publications/files.js.map +0 -1
  620. package/publications/flags-update.d.ts +0 -2
  621. package/publications/flags-update.js +0 -22
  622. package/publications/flags-update.js.map +0 -1
  623. package/publications/flags.d.ts +0 -2
  624. package/publications/flags.js +0 -22
  625. package/publications/flags.js.map +0 -1
  626. package/publications/logs.d.ts +0 -2
  627. package/publications/logs.js +0 -164
  628. package/publications/logs.js.map +0 -1
  629. package/publications/notifications.d.ts +0 -2
  630. package/publications/notifications.js +0 -16
  631. package/publications/notifications.js.map +0 -1
  632. package/publications/report-builder-dashboard-builders.d.ts +0 -2
  633. package/publications/report-builder-dashboard-builders.js +0 -42
  634. package/publications/report-builder-dashboard-builders.js.map +0 -1
  635. package/publications/report-builder-libraries.d.ts +0 -2
  636. package/publications/report-builder-libraries.js +0 -90
  637. package/publications/report-builder-libraries.js.map +0 -1
  638. package/publications/report-builder-reports.d.ts +0 -2
  639. package/publications/report-builder-reports.js +0 -50
  640. package/publications/report-builder-reports.js.map +0 -1
  641. package/publications/super-admin.d.ts +0 -2
  642. package/publications/super-admin.js +0 -16
  643. package/publications/super-admin.js.map +0 -1
  644. package/publications/user-groups.d.ts +0 -1
  645. package/publications/user-groups.js +0 -16
  646. package/publications/user-groups.js.map +0 -1
  647. package/publications/user-guides.d.ts +0 -1
  648. package/publications/user-guides.js +0 -16
  649. package/publications/user-guides.js.map +0 -1
  650. package/resolveio-server-app.d.ts +0 -70
  651. package/resolveio-server-app.js +0 -801
  652. package/resolveio-server-app.js.map +0 -1
  653. package/server-app.d.ts +0 -228
  654. package/server-app.js +0 -3566
  655. package/server-app.js.map +0 -1
  656. package/services/codex-client.d.ts +0 -128
  657. package/services/codex-client.js +0 -1629
  658. package/services/codex-client.js.map +0 -1
  659. package/services/openai-client.d.ts +0 -46
  660. package/services/openai-client.js +0 -318
  661. package/services/openai-client.js.map +0 -1
  662. package/types/error-report.d.ts +0 -25
  663. package/types/error-report.js +0 -4
  664. package/types/error-report.js.map +0 -1
  665. package/types/slow-query-report.d.ts +0 -27
  666. package/types/slow-query-report.js +0 -6
  667. package/types/slow-query-report.js.map +0 -1
  668. package/util/ai-qa-policy.d.ts +0 -124
  669. package/util/ai-qa-policy.js +0 -736
  670. package/util/ai-qa-policy.js.map +0 -1
  671. package/util/ai-run-evidence-adapters.d.ts +0 -109
  672. package/util/ai-run-evidence-adapters.js +0 -7234
  673. package/util/ai-run-evidence-adapters.js.map +0 -1
  674. package/util/ai-run-evidence-dashboard.d.ts +0 -88
  675. package/util/ai-run-evidence-dashboard.js +0 -343
  676. package/util/ai-run-evidence-dashboard.js.map +0 -1
  677. package/util/ai-run-evidence-eval.d.ts +0 -86
  678. package/util/ai-run-evidence-eval.js +0 -1018
  679. package/util/ai-run-evidence-eval.js.map +0 -1
  680. package/util/ai-run-evidence.d.ts +0 -244
  681. package/util/ai-run-evidence.js +0 -1096
  682. package/util/ai-run-evidence.js.map +0 -1
  683. package/util/ai-runner-artifacts.d.ts +0 -82
  684. package/util/ai-runner-artifacts.js +0 -713
  685. package/util/ai-runner-artifacts.js.map +0 -1
  686. package/util/ai-runner-manager-autopilot.d.ts +0 -210
  687. package/util/ai-runner-manager-autopilot.js +0 -642
  688. package/util/ai-runner-manager-autopilot.js.map +0 -1
  689. package/util/ai-runner-manager-policy.d.ts +0 -807
  690. package/util/ai-runner-manager-policy.js +0 -3501
  691. package/util/ai-runner-manager-policy.js.map +0 -1
  692. package/util/ai-runner-qa-auth.d.ts +0 -5
  693. package/util/ai-runner-qa-auth.js +0 -839
  694. package/util/ai-runner-qa-auth.js.map +0 -1
  695. package/util/ai-runner-qa-tools.d.ts +0 -26
  696. package/util/ai-runner-qa-tools.js +0 -3520
  697. package/util/ai-runner-qa-tools.js.map +0 -1
  698. package/util/aicoder-runner-v6.d.ts +0 -426
  699. package/util/aicoder-runner-v6.js +0 -2464
  700. package/util/aicoder-runner-v6.js.map +0 -1
  701. package/util/common.d.ts +0 -31
  702. package/util/common.js +0 -683
  703. package/util/common.js.map +0 -1
  704. package/util/customer-portal-password.d.ts +0 -13
  705. package/util/customer-portal-password.js +0 -209
  706. package/util/customer-portal-password.js.map +0 -1
  707. package/util/error-reporter.d.ts +0 -52
  708. package/util/error-reporter.js +0 -326
  709. package/util/error-reporter.js.map +0 -1
  710. package/util/error-tracking.d.ts +0 -13
  711. package/util/error-tracking.js +0 -120
  712. package/util/error-tracking.js.map +0 -1
  713. package/util/openai-usage-cost.d.ts +0 -6
  714. package/util/openai-usage-cost.js +0 -103
  715. package/util/openai-usage-cost.js.map +0 -1
  716. package/util/report-builder-unwinds.d.ts +0 -15
  717. package/util/report-builder-unwinds.js +0 -156
  718. package/util/report-builder-unwinds.js.map +0 -1
  719. package/util/runner-process-janitor.d.ts +0 -27
  720. package/util/runner-process-janitor.js +0 -208
  721. package/util/runner-process-janitor.js.map +0 -1
  722. package/util/schema-report-builder.d.ts +0 -6
  723. package/util/schema-report-builder.js +0 -481
  724. package/util/schema-report-builder.js.map +0 -1
  725. package/util/slow-query-reporter.d.ts +0 -28
  726. package/util/slow-query-reporter.js +0 -226
  727. package/util/slow-query-reporter.js.map +0 -1
  728. package/util/subscription-dependency-context.d.ts +0 -34
  729. package/util/subscription-dependency-context.js +0 -1283
  730. package/util/subscription-dependency-context.js.map +0 -1
  731. package/util/support-runner-v5.d.ts +0 -1426
  732. package/util/support-runner-v5.js +0 -7631
  733. package/util/support-runner-v5.js.map +0 -1
  734. package/util/tokenizer.d.ts +0 -5
  735. package/util/tokenizer.js +0 -41
  736. package/util/tokenizer.js.map +0 -1
  737. package/workers/codex-runner.worker.d.ts +0 -1
  738. package/workers/codex-runner.worker.js +0 -192
  739. package/workers/codex-runner.worker.js.map +0 -1
  740. /package/{private → src/private}/email-templates/enrollment.html +0 -0
  741. /package/{private → src/private}/email-templates/forgot-password.html +0 -0
  742. /package/{private → src/private}/email-templates/support-ticket-deleted.html +0 -0
  743. /package/{private → src/private}/email-templates/support-ticket-modified.html +0 -0
  744. /package/{private → src/private}/email-templates/support-ticket.html +0 -0
  745. /package/{public_api.d.ts → src/public_api.ts} +0 -0
@@ -0,0 +1,2614 @@
1
+ import axios from 'axios';
2
+ import * as Excel from 'exceljs';
3
+ import * as moment from 'moment-timezone';
4
+ import SimpleSchema from 'simpl-schema';
5
+ import { CronJobs } from '../collections/cron-job.collection';
6
+ import { CommunicationMetrics } from '../collections/communication-metric.collection';
7
+ import { Files } from '../collections/file.collection';
8
+ import { OpenAIUsageLedger } from '../collections/openai-usage-ledger.collection';
9
+ import { ReportBuilderReports } from '../collections/report-builder-report.collection';
10
+ import { MethodManager } from '../managers/method.manager';
11
+ import { PaginationOptions } from '../models/pagination.model';
12
+ import { ResolveIOServer } from '../resolveio-server-app';
13
+ import { deepCopy, mergeDeep, round, toTitleCase } from '../util/common';
14
+
15
+ function getFilterTargets(fieldPath: string, report: any): string[] {
16
+ if (!fieldPath) {
17
+ return [];
18
+ }
19
+
20
+ if (fieldPath.startsWith('layout_col_')) {
21
+ const normalized = fieldPath.replace(/^layout_col_/, '');
22
+ const parts = normalized.split('_');
23
+ const idx = parseInt(parts[parts.length - 1], 10);
24
+
25
+ if (!isNaN(idx)) {
26
+ const layoutTargets = (report?.fields_selected || [])
27
+ .filter(f => f && (f.layoutColumnIndex === idx || (f.id || '').includes(`f_layout_${idx}_`)))
28
+ .map(f => f.fieldPath);
29
+
30
+ if (layoutTargets.length) {
31
+ return layoutTargets;
32
+ }
33
+ }
34
+ }
35
+
36
+ return [fieldPath];
37
+ }
38
+
39
+ function buildFilterExpression(filter: any, path: string) {
40
+ if (!filter || !filter.condition || (!filter.field && !path)) {
41
+ return null;
42
+ }
43
+
44
+ const hasValue = filter.value !== null && (Array.isArray(filter.value) ? filter.value.length > 0 : true);
45
+
46
+ if (!hasValue && filter.condition !== 'null' && filter.condition !== 'nnull') {
47
+ return null;
48
+ }
49
+
50
+ if (filter.condition === 'bw') {
51
+ if (filter.fieldType === 'Date' && filter.is_rolling) {
52
+ let startDate = null;
53
+ let endDate = null;
54
+
55
+ if (filter.rolling_interval === 'Q1') {
56
+ startDate = moment().startOf('year').toDate();
57
+ endDate = moment().startOf('year').quarter(2).subtract(1, 'days').toDate();
58
+ }
59
+ else if (filter.rolling_interval === 'Q2') {
60
+ startDate = moment().startOf('year').quarter(2).toDate();
61
+ endDate = moment().startOf('year').quarter(3).subtract(1, 'days').toDate();
62
+ }
63
+ else if (filter.rolling_interval === 'Q3') {
64
+ startDate = moment().startOf('year').quarter(3).toDate();
65
+ endDate = moment().startOf('year').quarter(4).subtract(1, 'days').toDate();
66
+ }
67
+ else if (filter.rolling_interval === 'Q4') {
68
+ startDate = moment().startOf('year').quarter(4).toDate();
69
+ endDate = moment().endOf('year').toDate();
70
+ }
71
+ else if (filter.rolling_interval === 'Today') {
72
+ startDate = moment().startOf('day').toDate();
73
+ endDate = moment().endOf('day').toDate();
74
+ }
75
+ else if (filter.rolling_interval === 'Yesterday') {
76
+ startDate = moment().subtract(1, 'days').startOf('day').toDate();
77
+ endDate = moment().subtract(1, 'days').endOf('day').toDate();
78
+ }
79
+ else if (filter.rolling_interval === 'Week') {
80
+ startDate = moment().startOf('isoWeek').toDate();
81
+ endDate = moment().endOf('day').toDate();
82
+ }
83
+ else if (filter.rolling_interval === 'Last Week') {
84
+ endDate = moment().startOf('isoWeek').subtract(1, 'days').toDate();
85
+ startDate = moment(endDate).startOf('isoWeek').toDate();
86
+ }
87
+ else if (filter.rolling_interval === 'Month') {
88
+ startDate = moment().startOf('month').toDate();
89
+ endDate = moment().endOf('day').toDate();
90
+ }
91
+ else if (filter.rolling_interval === 'Last Month') {
92
+ endDate = moment().startOf('month').subtract(1, 'days').endOf('day').toDate();
93
+ startDate = moment(endDate).startOf('month').toDate();
94
+ }
95
+ else if (filter.rolling_interval === 'Last 30 Days') {
96
+ endDate = moment().endOf('day').toDate();
97
+ startDate = moment().subtract(30, 'days').startOf('day').toDate();
98
+ }
99
+ else if (filter.rolling_interval === 'Quarter') {
100
+ startDate = moment().startOf('year').quarter(moment().quarter()).toDate();
101
+ endDate = moment().endOf('day').toDate();
102
+ }
103
+ else if (filter.rolling_interval === 'Last Quarter') {
104
+ endDate = moment().startOf('year').quarter(moment().quarter()).subtract(1, 'days').endOf('day').toDate();
105
+ startDate = moment(endDate).startOf('quarter').toDate();
106
+ }
107
+ else if (filter.rolling_interval === 'Year') {
108
+ startDate = moment().startOf('year').toDate();
109
+ endDate = moment().endOf('day').toDate();
110
+ }
111
+ else if (filter.rolling_interval === 'Last Year') {
112
+ endDate = moment().startOf('year').subtract(1, 'days').endOf('day').toDate();
113
+ startDate = moment(endDate).startOf('year').toDate();
114
+ }
115
+ else if (filter.rolling_interval === 'All') {
116
+ startDate = new Date(2017, 0, 1, 0, 0, 0, 0);
117
+ endDate = moment().endOf('day').toDate();
118
+ }
119
+
120
+ return {$and: [{[path]: {'$gte': startDate}}, {[path]: {'$lte': endDate}}]};
121
+ }
122
+
123
+ return {$and: [{[path]: {'$gte': filter.value}}, {[path]: {'$lte': filter.highValue}}]};
124
+ }
125
+ else if (filter.condition === 'nnull') {
126
+ let and = [];
127
+ and.push({[path]: {'$exists': true}});
128
+ and.push({[path]: {'$ne': null}});
129
+
130
+ if (filter.fieldType === 'String') {
131
+ and.push({[path]: {'$ne': ''}});
132
+ }
133
+
134
+ return {$and: and};
135
+ }
136
+ else if (filter.condition === 'null') {
137
+ return {[path]: {'$eq': null}};
138
+ }
139
+ else {
140
+ if (Array.isArray(filter.value)) {
141
+ if (filter.condition === 'ne') {
142
+ let and = [];
143
+ filter.value.forEach(filt => {
144
+ and.push({[path]: {['$' + filter.condition]: (Array.isArray(filt) ? filt[0] : filt)}});
145
+ });
146
+
147
+ if (and.length > 1) {
148
+ return {$and: and};
149
+ }
150
+ else {
151
+ return and[0];
152
+ }
153
+ }
154
+ else {
155
+ let or = [];
156
+
157
+ filter.value.forEach(filt => {
158
+ or.push({[path]: {['$' + filter.condition]: (Array.isArray(filt) ? filt[0] : filt)}});
159
+ });
160
+
161
+ if (or.length > 1) {
162
+ return {$or: or};
163
+ }
164
+ else {
165
+ return or[0];
166
+ }
167
+ }
168
+ }
169
+ else {
170
+ return {[path]: {['$' + filter.condition]: filter.value}};
171
+ }
172
+ }
173
+
174
+ return null;
175
+ }
176
+
177
+ function parseLayoutColIndex(fieldRef: string): number | null {
178
+ if (!fieldRef || !fieldRef.startsWith('layout_col_')) {
179
+ return null;
180
+ }
181
+
182
+ const normalized = fieldRef.replace(/^layout_col_/, '');
183
+ const parts = normalized.split('_');
184
+ const idx = parseInt(parts[parts.length - 1], 10);
185
+
186
+ return isNaN(idx) ? null : idx;
187
+ }
188
+
189
+ function resolveLayoutColumnPrimaryLeafId(fieldRef: string, report: any) {
190
+ const idx = parseLayoutColIndex(fieldRef);
191
+
192
+ if (idx === null) {
193
+ return null;
194
+ }
195
+
196
+ const candidates = (report?.fields_selected || []).filter(f => f && typeof f.layoutColumnIndex !== 'undefined' && f.layoutColumnIndex === idx);
197
+
198
+ if (!candidates.length) {
199
+ return null;
200
+ }
201
+
202
+ const root = report?.collection_root;
203
+ const preferred = candidates.find(c => c.collection_name === root || c.layoutCollection === root) || candidates[0];
204
+
205
+ return preferred?.id || null;
206
+ }
207
+
208
+ function resolveFieldRefToLeafId(fieldRef: string, report: any) {
209
+ if (!fieldRef) {
210
+ return null;
211
+ }
212
+
213
+ if (fieldRef.startsWith('layout_col_')) {
214
+ return resolveLayoutColumnPrimaryLeafId(fieldRef, report);
215
+ }
216
+
217
+ return fieldRef;
218
+ }
219
+
220
+ function buildCollectionKeysFromReport(report: any): string[] {
221
+ const keys: string[] = [];
222
+
223
+ if (report?.collection_root) {
224
+ keys.push(report.collection_root);
225
+ }
226
+
227
+ (report?.collection_joins || []).forEach(join => {
228
+ if (!join) {
229
+ return;
230
+ }
231
+
232
+ const alias = (join.alias || '').trim();
233
+ if (alias && !keys.includes(alias)) {
234
+ keys.push(alias);
235
+ }
236
+ else if (join.collection && !keys.includes(join.collection)) {
237
+ keys.push(join.collection);
238
+ }
239
+ });
240
+
241
+ return keys.length ? keys : ['root'];
242
+ }
243
+
244
+ function buildLayoutColumnsFromFields(report: any, fields: any[] = []) {
245
+ const keys = buildCollectionKeysFromReport(report);
246
+
247
+ return (fields || []).map((field, index) => {
248
+ const header = field.columnName || field.fieldName || `Column ${index + 1}`;
249
+ const isCustom = ((field?.id || field?.fieldPath || '') + '').startsWith('c_') || field?.fieldPathName === 'Custom Field';
250
+
251
+ const mappings = keys.map(key => {
252
+ const matchesKey = key === report.collection_root
253
+ ? (field.collection_name === report.collection_root || isCustom)
254
+ : (key === (field.lookup_as || '').trim()) || key === field.collection_name;
255
+
256
+ if (matchesKey) {
257
+ return {
258
+ collection: key,
259
+ mode: 'variable',
260
+ fieldPath: field.fieldPath || '',
261
+ fieldId: field.id || `f_layout_${index}`,
262
+ text: '',
263
+ leafValueType: field.leafValueType || 'Value',
264
+ leafFormatType: field.leafFormatType || ''
265
+ };
266
+ }
267
+
268
+ return {
269
+ collection: key,
270
+ mode: 'text',
271
+ fieldPath: '',
272
+ fieldId: '',
273
+ text: '',
274
+ leafValueType: 'Value',
275
+ leafFormatType: ''
276
+ };
277
+ });
278
+
279
+ return {
280
+ id: field.id ? `layout_${field.id}` : `layout_${index}`,
281
+ header,
282
+ mappings
283
+ };
284
+ });
285
+ }
286
+
287
+ function ensureLayoutHasCustomField(report: any, layout: any[] = [], customId: string, columnName?: string, leafValueType?: string) {
288
+ if (!layout || !customId) {
289
+ return;
290
+ }
291
+
292
+ const hasCustomField = (layout || []).some(col => (col?.mappings || []).some(m =>
293
+ m?.mode === 'variable' && (m.fieldId === customId || m.fieldPath === customId)
294
+ ));
295
+ if (hasCustomField) {
296
+ return;
297
+ }
298
+
299
+ const keys = buildCollectionKeysFromReport(report);
300
+ const rootKey = report?.collection_root || '';
301
+ const header = columnName || customId;
302
+
303
+ const mappings = keys.map(key => {
304
+ if (key === rootKey) {
305
+ return {
306
+ collection: key,
307
+ mode: 'variable',
308
+ fieldPath: customId,
309
+ fieldId: customId,
310
+ text: '',
311
+ leafValueType: leafValueType || 'Value',
312
+ leafFormatType: 'Number'
313
+ };
314
+ }
315
+
316
+ return {
317
+ collection: key,
318
+ mode: 'text',
319
+ fieldPath: '',
320
+ fieldId: '',
321
+ text: '',
322
+ leafValueType: 'Value',
323
+ leafFormatType: ''
324
+ };
325
+ });
326
+
327
+ layout.push({
328
+ id: `layout_${customId}`,
329
+ header,
330
+ mappings
331
+ });
332
+ }
333
+
334
+ function ensureLayoutIncludesCustomFields(report: any, layout: any[] = []) {
335
+ const fieldsCustom = report?.fields_custom || [];
336
+ if (!fieldsCustom.length) {
337
+ return layout;
338
+ }
339
+
340
+ fieldsCustom.forEach(custom => {
341
+ ensureLayoutHasCustomField(report, layout, custom?.id, custom?.columnName, custom?.leafValueType);
342
+ });
343
+
344
+ return layout;
345
+ }
346
+
347
+ function getLayoutColumnsFromReport(report: any) {
348
+ let layout: any[] = [];
349
+
350
+ if (report?.fields_layout && report.fields_layout.length) {
351
+ layout = deepCopy(report.fields_layout);
352
+ }
353
+ else {
354
+ layout = buildLayoutColumnsFromFields(report, report?.fields_selected || []);
355
+ }
356
+
357
+ return ensureLayoutIncludesCustomFields(report, layout);
358
+ }
359
+
360
+ function syncSelectedFieldsFromLayout(report: any, layoutColumns: any[] = []) {
361
+ const fields: any[] = [];
362
+ const sourceFields = deepCopy(report?.fields_selected || []);
363
+ const byId = new Map<string, any>();
364
+ const byPath = new Map<string, any>();
365
+
366
+ sourceFields.forEach(f => {
367
+ if (f?.id) {
368
+ byId.set(f.id, f);
369
+ }
370
+ if (f?.fieldPath) {
371
+ byPath.set(f.fieldPath, f);
372
+ }
373
+ });
374
+
375
+ layoutColumns.forEach((col, colIndex) => {
376
+ (col.mappings || []).forEach((map, mapIndex) => {
377
+ if (map.mode === 'variable' && map.fieldPath) {
378
+ const baseField = byId.get(map.fieldId) || byPath.get(map.fieldPath);
379
+
380
+ if (!baseField) {
381
+ return;
382
+ }
383
+
384
+ const clone = deepCopy(baseField);
385
+ clone.columnName = col.header || baseField.columnName || baseField.fieldPathName || 'Column';
386
+ clone.show = (typeof map.show === 'boolean')
387
+ ? map.show
388
+ : (typeof baseField.show === 'boolean' ? baseField.show : true);
389
+ clone.leafValueType = map.leafValueType || clone.leafValueType;
390
+ clone.leafFormatType = map.leafFormatType || clone.leafFormatType;
391
+ clone.id = map.fieldId || clone.id || `f_layout_${colIndex}_${mapIndex}`;
392
+ clone.layoutCollection = map.collection;
393
+ clone.layoutColumnIndex = colIndex;
394
+
395
+ if (!map.fieldId) {
396
+ map.fieldId = clone.id;
397
+ }
398
+
399
+ fields.push(clone);
400
+ }
401
+ });
402
+ });
403
+
404
+ return fields;
405
+ }
406
+
407
+ function materializeLayoutRow(report: any, layoutColumns: any[], selectedFields: any[], row: any) {
408
+ const materialized: any = {_id: row?._id};
409
+
410
+ const hasDataForCollection = (collection: string) => {
411
+ if (!collection) {
412
+ return false;
413
+ }
414
+
415
+ return (selectedFields || []).some(f =>
416
+ f.layoutCollection === collection &&
417
+ row &&
418
+ Object.prototype.hasOwnProperty.call(row, f.id) &&
419
+ row[f.id] !== undefined &&
420
+ row[f.id] !== null &&
421
+ row[f.id] !== ''
422
+ );
423
+ };
424
+
425
+ (layoutColumns || []).forEach((col, colIndex) => {
426
+ const values: any[] = [];
427
+
428
+ (col.mappings || []).forEach(map => {
429
+ if (map.mode === 'text' && map.text) {
430
+ if (hasDataForCollection(map.collection)) {
431
+ values.push({collection: map.collection, value: map.text});
432
+ }
433
+ }
434
+ else if (map.mode === 'variable' && map.fieldId && row && Object.prototype.hasOwnProperty.call(row, map.fieldId)) {
435
+ values.push({collection: map.collection, value: row[map.fieldId]});
436
+ }
437
+ });
438
+
439
+ materialized[colIndex] = values;
440
+ });
441
+
442
+ return materialized;
443
+ }
444
+
445
+ function getLayoutCellExportValue(report: any, layoutRow: any, colIndex: number) {
446
+ if (!layoutRow || layoutRow[colIndex] === undefined || layoutRow[colIndex] === null) {
447
+ return '';
448
+ }
449
+
450
+ const cellValues = Array.isArray(layoutRow[colIndex]) ? layoutRow[colIndex] : [layoutRow[colIndex]];
451
+ const filtered = cellValues.filter(v => v !== undefined && v !== null && v.value !== undefined && v.value !== null && v.value !== '');
452
+
453
+ if (!filtered.length) {
454
+ return '';
455
+ }
456
+
457
+ const rootCollection = report?.collection_root;
458
+ const preferred = filtered.find(v => v.collection === rootCollection) || filtered[0];
459
+ const value = preferred?.value;
460
+
461
+ if (value === undefined || value === null) {
462
+ return '';
463
+ }
464
+
465
+ if (Array.isArray(value)) {
466
+ return value;
467
+ }
468
+
469
+ return typeof value === 'string' ? value.trim().replace(/\t/g, '').replace(/\n/g, '') : value;
470
+ }
471
+
472
+ function getPrimaryFieldForLayoutColumn(col: any, selectedFields: any[]) {
473
+ if (!col?.mappings) {
474
+ return null;
475
+ }
476
+
477
+ const target = col.mappings.find(m => m.mode === 'variable' && m.fieldId);
478
+
479
+ if (!target) {
480
+ return null;
481
+ }
482
+
483
+ return (selectedFields || []).find(f => f.id === target.fieldId) || null;
484
+ }
485
+
486
+ const STRING_DATE_FORMATS = [
487
+ moment.ISO_8601,
488
+ 'MM/DD/YYYY',
489
+ 'M/D/YYYY',
490
+ 'MM/DD/YYYY h:mm A',
491
+ 'M/D/YYYY h:mm A',
492
+ 'MM/DD/YYYY hh:mm A',
493
+ 'M/D/YYYY hh:mm A',
494
+ 'MM/DD/YYYY h:mm:ss A',
495
+ 'M/D/YYYY h:mm:ss A',
496
+ 'MM/DD/YYYY hh:mm:ss A',
497
+ 'M/D/YYYY hh:mm:ss A',
498
+ 'MM/DD/YYYY H:mm',
499
+ 'M/D/YYYY H:mm',
500
+ 'MM/DD/YYYY HH:mm',
501
+ 'M/D/YYYY HH:mm',
502
+ 'MM/DD/YYYY H:mm:ss',
503
+ 'M/D/YYYY H:mm:ss',
504
+ 'MM/DD/YYYY HH:mm:ss',
505
+ 'M/D/YYYY HH:mm:ss'
506
+ ];
507
+
508
+ function parseMoment(value: any) {
509
+ if (value === null || value === undefined) {
510
+ return null;
511
+ }
512
+
513
+ if (moment.isMoment(value)) {
514
+ return value;
515
+ }
516
+
517
+ if (value instanceof Date || typeof value === 'number') {
518
+ const m = moment(value);
519
+ return m.isValid() ? m : null;
520
+ }
521
+
522
+ if (typeof value === 'string' || value instanceof String) {
523
+ const str = `${value}`.trim();
524
+ if (!str) {
525
+ return null;
526
+ }
527
+
528
+ const m = moment(str, STRING_DATE_FORMATS, true);
529
+ if (m.isValid()) {
530
+ return m;
531
+ }
532
+
533
+ const loose = moment(str, STRING_DATE_FORMATS, false);
534
+ if (loose.isValid()) {
535
+ return loose;
536
+ }
537
+
538
+ const jsDateStr = str.replace(/\s*\([^)]*\)\s*$/, '');
539
+ const jsDate = moment(jsDateStr, 'ddd MMM DD YYYY HH:mm:ss [GMT]ZZ', true);
540
+ if (jsDate.isValid()) {
541
+ return jsDate;
542
+ }
543
+
544
+ if (/[T:\\/\\-]/.test(str) || /GMT|UTC/i.test(str)) {
545
+ const fallback = moment(str);
546
+ return fallback.isValid() ? fallback : null;
547
+ }
548
+
549
+ return null;
550
+ }
551
+
552
+ const m = moment(value);
553
+ return m.isValid() ? m : null;
554
+ }
555
+
556
+ function parseExcelNumber(value: unknown): number | null {
557
+ if (typeof value === 'number') {
558
+ return Number.isFinite(value) ? value : null;
559
+ }
560
+
561
+ if (typeof value !== 'string') {
562
+ return null;
563
+ }
564
+
565
+ let s = value.trim();
566
+ if (!s) {
567
+ return null;
568
+ }
569
+
570
+ let negative = false;
571
+ const parenMatch = s.match(/^\((.*)\)$/);
572
+ if (parenMatch) {
573
+ negative = true;
574
+ s = parenMatch[1].trim();
575
+ }
576
+
577
+ s = s
578
+ .replace(/[\t\n\r]/g, '')
579
+ .replace(/\$/g, '')
580
+ .replace(/,/g, '');
581
+
582
+ s = s.trim();
583
+ if (!s) {
584
+ return null;
585
+ }
586
+
587
+ const num = Number(s);
588
+ if (!Number.isFinite(num)) {
589
+ return null;
590
+ }
591
+
592
+ return negative ? -num : num;
593
+ }
594
+
595
+ function isExcelNumericField(field: any): boolean {
596
+ return !!field && (
597
+ field.fieldType === 'Number' ||
598
+ field.leafValueType === 'Count' ||
599
+ field.leafValueType === 'Sum' ||
600
+ field.leafValueType === 'Average'
601
+ );
602
+ }
603
+
604
+ function writeExcelCell(cell: Excel.Cell, value: any, fieldMeta?: any) {
605
+ if (value === null || value === undefined) {
606
+ cell.value = '';
607
+ return;
608
+ }
609
+
610
+ if (fieldMeta?.fieldType === 'Date') {
611
+ const rawValue = Array.isArray(value) ? value.find(v => v !== null && v !== undefined && v !== '') : value;
612
+ const m = parseMoment(rawValue);
613
+ if (m) {
614
+ const timezone = process.env.TZ_CLIENT;
615
+ const local = timezone ? moment.tz(m.toDate(), timezone) : m.clone().local();
616
+ const leafFormatType = fieldMeta?.leafFormatType;
617
+ const excelSerial = (Date.UTC(
618
+ local.year(),
619
+ local.month(),
620
+ local.date(),
621
+ local.hour(),
622
+ local.minute(),
623
+ local.second(),
624
+ local.millisecond(),
625
+ ) - Date.UTC(1899, 11, 30)) / 86400000;
626
+ if (leafFormatType === 'Date' || leafFormatType === 'Date_long') {
627
+ cell.value = Math.floor(excelSerial);
628
+ }
629
+ else if (leafFormatType === 'Time' || leafFormatType === 'DateTime' || leafFormatType === 'DateTime_long') {
630
+ cell.value = excelSerial;
631
+ }
632
+ else {
633
+ cell.value = local.toDate();
634
+ }
635
+
636
+ switch (leafFormatType) {
637
+ case 'Time':
638
+ cell.numFmt = 'h:mm AM/PM';
639
+ break;
640
+ case 'DateTime':
641
+ case 'DateTime_long':
642
+ cell.numFmt = 'mm/dd/yyyy h:mm AM/PM';
643
+ break;
644
+ case 'Date':
645
+ cell.numFmt = 'mm/dd/yyyy';
646
+ break;
647
+ case 'Date_long':
648
+ default:
649
+ cell.numFmt = 'mm/dd/yyyy';
650
+ break;
651
+ }
652
+
653
+ return;
654
+ }
655
+ }
656
+
657
+ if (Array.isArray(value)) {
658
+ cell.value = value.join(', ');
659
+ return;
660
+ }
661
+
662
+ if (isExcelNumericField(fieldMeta)) {
663
+ const parsed = parseExcelNumber(value);
664
+ if (parsed !== null) {
665
+ cell.value = parsed;
666
+
667
+ if (fieldMeta?.leafFormatType === 'Currency') {
668
+ cell.numFmt = '"$"#,##0.00;[Red]-"$"#,##0.00';
669
+ }
670
+ else if (fieldMeta?.leafFormatType === 'Number' || fieldMeta?.leafValueType === 'Count') {
671
+ cell.numFmt = '#,##0';
672
+ }
673
+
674
+ return;
675
+ }
676
+ }
677
+
678
+ if (typeof value === 'string') {
679
+ cell.value = value.replace(/\t/g, '').replace(/\n/g, '');
680
+ }
681
+ else {
682
+ cell.value = value;
683
+ }
684
+ }
685
+
686
+ function getMaxRows(row) {
687
+ let keys = Object.keys(row || {});
688
+ let maxArray = [];
689
+
690
+ for (let i = 0; i < keys.length; i++) {
691
+ if (Array.isArray(row[keys[i]]) && row[keys[i]].length > maxArray.length) {
692
+ maxArray = row[keys[i]];
693
+ }
694
+ else if (maxArray.length === 0) {
695
+ maxArray = [row[keys[i]]];
696
+ }
697
+ }
698
+
699
+ return maxArray;
700
+ }
701
+
702
+ function applyClientGroupSortToData(results: any[], report: any, sorts: any[] = []) {
703
+ if (!Array.isArray(results) || !results.length) {
704
+ return;
705
+ }
706
+
707
+ const groupIds = (report?.groups_row || []).map(g => g.id).filter(Boolean);
708
+
709
+ if (!groupIds.length) {
710
+ return;
711
+ }
712
+
713
+ const groupOrderById = {};
714
+ const leafSorts = [];
715
+
716
+ (sorts || []).forEach(s => {
717
+ if (!s?.field) {
718
+ return;
719
+ }
720
+
721
+ if (groupIds.includes(s.field)) {
722
+ groupOrderById[s.field] = s.order || 'asc';
723
+ }
724
+ else {
725
+ const leafId = resolveFieldRefToLeafId(s.field, report);
726
+ if (leafId) {
727
+ leafSorts.push({leafId, order: s.order || 'asc'});
728
+ }
729
+ }
730
+ });
731
+
732
+ const compareScalar = (aVal: any, bVal: any, order: string) => {
733
+ const dir = order === 'desc' ? -1 : 1;
734
+
735
+ const aNull = aVal === null || aVal === undefined || aVal === '';
736
+ const bNull = bVal === null || bVal === undefined || bVal === '';
737
+ if (aNull && bNull) { return 0; }
738
+ if (aNull) { return 1; }
739
+ if (bNull) { return -1; }
740
+
741
+ const aNum = typeof aVal === 'number' ? aVal : Number(aVal);
742
+ const bNum = typeof bVal === 'number' ? bVal : Number(bVal);
743
+ if (!isNaN(aNum) && !isNaN(bNum)) {
744
+ if (aNum === bNum) { return 0; }
745
+ return aNum > bNum ? dir : -dir;
746
+ }
747
+
748
+ const aStr = ('' + aVal).toLowerCase();
749
+ const bStr = ('' + bVal).toLowerCase();
750
+ if (aStr === bStr) { return 0; }
751
+ return aStr > bStr ? dir : -dir;
752
+ };
753
+
754
+ const selectedIds = (report?.fields_selected || []).map(f => f.id).filter(Boolean);
755
+
756
+ const sortLeafArraysInRow = (row: any) => {
757
+ if (!row || !leafSorts.length) {
758
+ return;
759
+ }
760
+
761
+ const arraysToReorder = selectedIds.filter(id => Array.isArray(row[id]));
762
+
763
+ if (!arraysToReorder.length) {
764
+ return;
765
+ }
766
+
767
+ let length = 0;
768
+ arraysToReorder.forEach(id => {
769
+ length = Math.max(length, row[id].length || 0);
770
+ });
771
+
772
+ if (length <= 1) {
773
+ return;
774
+ }
775
+
776
+ const getArrayVal = (id: string, idx: number) => {
777
+ const arr = row[id];
778
+ if (!Array.isArray(arr) || !arr.length) {
779
+ return null;
780
+ }
781
+ return arr[idx] !== undefined ? arr[idx] : arr[arr.length - 1];
782
+ };
783
+
784
+ const indices = Array.from({length}, (_, i) => i);
785
+ indices.sort((i, j) => {
786
+ for (const s of leafSorts) {
787
+ const aVal = Array.isArray(row[s.leafId]) ? getArrayVal(s.leafId, i) : row[s.leafId];
788
+ const bVal = Array.isArray(row[s.leafId]) ? getArrayVal(s.leafId, j) : row[s.leafId];
789
+ const cmp = compareScalar(aVal, bVal, s.order);
790
+ if (cmp !== 0) {
791
+ return cmp;
792
+ }
793
+ }
794
+ return 0;
795
+ });
796
+
797
+ arraysToReorder.forEach(id => {
798
+ row[id] = indices.map(idx => getArrayVal(id, idx));
799
+ });
800
+ };
801
+
802
+ const sortGroupLevel = (rows: any[], depth: number) => {
803
+ if (!Array.isArray(rows) || !rows.length) {
804
+ return;
805
+ }
806
+
807
+ const groupId = groupIds[depth];
808
+ const order = groupOrderById[groupId] || 'asc';
809
+ rows.sort((a, b) => compareScalar(a?._id?.[groupId], b?._id?.[groupId], order));
810
+
811
+ if (depth < groupIds.length - 1) {
812
+ const childId = groupIds[depth + 1];
813
+ rows.forEach(r => {
814
+ const children = r ? r[childId] : null;
815
+ if (Array.isArray(children)) {
816
+ sortGroupLevel(children, depth + 1);
817
+ }
818
+ });
819
+ }
820
+ else {
821
+ rows.forEach(r => sortLeafArraysInRow(r));
822
+ }
823
+ };
824
+
825
+ sortGroupLevel(results, 0);
826
+ }
827
+
828
+ const OPENAI_USAGE_BILLING_ENDPOINT = 'https://backend.resolveio.com/api/openai-usage/report';
829
+ const AI_USAGE_SYNC_JOB_NAME = 'AI Usage Billing Sync';
830
+ const LEGACY_AI_USAGE_SYNC_JOB_NAME = 'OpenAI Usage Billing Sync';
831
+ const OPENAI_USAGE_DEFAULT_LOOKBACK_DAYS = 30;
832
+ const OPENAI_USAGE_DEFAULT_BATCH_SIZE = 500;
833
+ const OPENAI_USAGE_DEFAULT_OVERLAP_MINUTES = 10;
834
+ const COMMUNICATION_METRICS_ENDPOINT = 'https://backend.resolveio.com/api/communication-metrics/report';
835
+ const COMMUNICATION_METRICS_DEFAULT_LOOKBACK_DAYS = 30;
836
+ const COMMUNICATION_METRICS_DEFAULT_BATCH_SIZE = 1000;
837
+ const COMMUNICATION_METRICS_DEFAULT_OVERLAP_MINUTES = 10;
838
+
839
+ const toNumberSetting = (value: any, fallback: number, allowZero = false): number => {
840
+ const parsed = Number(value);
841
+ if (!Number.isFinite(parsed)) {
842
+ return fallback;
843
+ }
844
+ if (allowZero && parsed === 0) {
845
+ return 0;
846
+ }
847
+ return parsed > 0 ? Math.floor(parsed) : fallback;
848
+ };
849
+
850
+ const normalizeString = (value: any): string => {
851
+ if (typeof value !== 'string') {
852
+ return '';
853
+ }
854
+ return value.trim();
855
+ };
856
+
857
+ const normalizeIdentifier = (value: any): string => {
858
+ if (value === undefined || value === null) {
859
+ return '';
860
+ }
861
+ return String(value).trim();
862
+ };
863
+
864
+ const normalizeDate = (value: any): Date | null => {
865
+ if (!value) {
866
+ return null;
867
+ }
868
+ const date = value instanceof Date ? value : new Date(value);
869
+ if (Number.isNaN(date.getTime())) {
870
+ return null;
871
+ }
872
+ return date;
873
+ };
874
+
875
+ const resolveOpenAIUsageSyncConfig = () => {
876
+ const config = ResolveIOServer.getServerConfig?.() || {};
877
+ const endpoint = normalizeString(
878
+ config['OPENAI_USAGE_BILLING_URL']
879
+ || config['OPENAI_USAGE_REPORT_URL']
880
+ || process.env.OPENAI_USAGE_BILLING_URL
881
+ || process.env.OPENAI_USAGE_REPORT_URL
882
+ ) || OPENAI_USAGE_BILLING_ENDPOINT;
883
+ const apiKey = normalizeString(
884
+ config['OPENAI_USAGE_INGEST_KEY']
885
+ || config['OPENAI_USAGE_API_KEY']
886
+ || process.env.OPENAI_USAGE_INGEST_KEY
887
+ || process.env.OPENAI_USAGE_API_KEY
888
+ || process.env.OPENAI_USAGE_REPORT_KEY
889
+ );
890
+ const batchSize = toNumberSetting(
891
+ config['OPENAI_USAGE_SYNC_BATCH_SIZE'] || process.env.OPENAI_USAGE_SYNC_BATCH_SIZE,
892
+ OPENAI_USAGE_DEFAULT_BATCH_SIZE
893
+ );
894
+ const lookbackDays = toNumberSetting(
895
+ config['OPENAI_USAGE_SYNC_LOOKBACK_DAYS'] || process.env.OPENAI_USAGE_SYNC_LOOKBACK_DAYS,
896
+ OPENAI_USAGE_DEFAULT_LOOKBACK_DAYS
897
+ );
898
+ const overlapMinutes = toNumberSetting(
899
+ config['OPENAI_USAGE_SYNC_OVERLAP_MINUTES'] || process.env.OPENAI_USAGE_SYNC_OVERLAP_MINUTES,
900
+ OPENAI_USAGE_DEFAULT_OVERLAP_MINUTES,
901
+ true
902
+ );
903
+ const enabledRaw = config['OPENAI_USAGE_SYNC_ENABLED'] ?? process.env.OPENAI_USAGE_SYNC_ENABLED;
904
+ const enabled = typeof enabledRaw === 'string'
905
+ ? enabledRaw.trim().toLowerCase() !== 'false' && enabledRaw.trim() !== '0'
906
+ : enabledRaw !== false;
907
+
908
+ return { config, endpoint, apiKey, batchSize, lookbackDays, overlapMinutes, enabled };
909
+ };
910
+
911
+ const buildOpenAIUsageQuery = (cursorAt: Date, cursorId: string) => {
912
+ const base: any = {
913
+ billable: { $ne: false }
914
+ };
915
+
916
+ if (!cursorId) {
917
+ base.timestamp = { $gte: cursorAt };
918
+ return base;
919
+ }
920
+
921
+ base.$or = [
922
+ { timestamp: { $gt: cursorAt } },
923
+ { timestamp: cursorAt, _id: { $gt: cursorId } }
924
+ ];
925
+ return base;
926
+ };
927
+
928
+ const normalizeUsagePayloadEntry = (entry: any) => {
929
+ if (!entry) {
930
+ return null;
931
+ }
932
+ const timestamp = normalizeDate(entry.timestamp);
933
+ const idClient = normalizeIdentifier(entry.id_client);
934
+ if (!timestamp || !idClient) {
935
+ return null;
936
+ }
937
+ const id = normalizeIdentifier(entry._id) || normalizeIdentifier(entry.id) || '';
938
+
939
+ return {
940
+ _id: id || undefined,
941
+ id_client: idClient,
942
+ timestamp: timestamp,
943
+ model: normalizeString(entry.model) || 'unknown',
944
+ input_tokens: Number(entry.input_tokens || 0),
945
+ output_tokens: Number(entry.output_tokens || 0),
946
+ total_tokens: Number(entry.total_tokens || 0),
947
+ cost_estimate: Number(entry.cost_estimate || 0),
948
+ billable: entry.billable !== false,
949
+ category: normalizeString(entry.category),
950
+ id_request: normalizeIdentifier(entry.id_request),
951
+ id_conversation: normalizeIdentifier(entry.id_conversation)
952
+ };
953
+ };
954
+
955
+ const resolveCommunicationMetricsSyncConfig = () => {
956
+ const config = ResolveIOServer.getServerConfig?.() || {};
957
+ const endpoint = normalizeString(
958
+ config['COMMUNICATION_METRICS_REPORT_URL']
959
+ || config['COMMUNICATION_METRICS_BILLING_URL']
960
+ || process.env.COMMUNICATION_METRICS_REPORT_URL
961
+ || process.env.COMMUNICATION_METRICS_BILLING_URL
962
+ ) || COMMUNICATION_METRICS_ENDPOINT;
963
+ const apiKey = normalizeString(
964
+ config['COMMUNICATION_METRICS_INGEST_KEY']
965
+ || config['COMMUNICATION_METRICS_API_KEY']
966
+ || process.env.COMMUNICATION_METRICS_INGEST_KEY
967
+ || process.env.COMMUNICATION_METRICS_API_KEY
968
+ || config['OPENAI_USAGE_INGEST_KEY']
969
+ || config['OPENAI_USAGE_API_KEY']
970
+ || process.env.OPENAI_USAGE_INGEST_KEY
971
+ || process.env.OPENAI_USAGE_API_KEY
972
+ || process.env.OPENAI_USAGE_REPORT_KEY
973
+ );
974
+ const batchSize = toNumberSetting(
975
+ config['COMMUNICATION_METRICS_SYNC_BATCH_SIZE'] || process.env.COMMUNICATION_METRICS_SYNC_BATCH_SIZE,
976
+ COMMUNICATION_METRICS_DEFAULT_BATCH_SIZE
977
+ );
978
+ const lookbackDays = toNumberSetting(
979
+ config['COMMUNICATION_METRICS_SYNC_LOOKBACK_DAYS'] || process.env.COMMUNICATION_METRICS_SYNC_LOOKBACK_DAYS,
980
+ COMMUNICATION_METRICS_DEFAULT_LOOKBACK_DAYS
981
+ );
982
+ const overlapMinutes = toNumberSetting(
983
+ config['COMMUNICATION_METRICS_SYNC_OVERLAP_MINUTES'] || process.env.COMMUNICATION_METRICS_SYNC_OVERLAP_MINUTES,
984
+ COMMUNICATION_METRICS_DEFAULT_OVERLAP_MINUTES,
985
+ true
986
+ );
987
+ const enabledRaw = config['COMMUNICATION_METRICS_SYNC_ENABLED'] ?? process.env.COMMUNICATION_METRICS_SYNC_ENABLED;
988
+ const enabled = typeof enabledRaw === 'string'
989
+ ? enabledRaw.trim().toLowerCase() !== 'false' && enabledRaw.trim() !== '0'
990
+ : enabledRaw !== false;
991
+
992
+ return { config, endpoint, apiKey, batchSize, lookbackDays, overlapMinutes, enabled };
993
+ };
994
+
995
+ const buildCommunicationMetricsQuery = (cursorAt: Date, cursorId: string) => {
996
+ if (!cursorId) {
997
+ return { date: { $gte: cursorAt } };
998
+ }
999
+ return {
1000
+ $or: [
1001
+ { date: { $gt: cursorAt } },
1002
+ { date: cursorAt, _id: { $gt: cursorId } }
1003
+ ]
1004
+ };
1005
+ };
1006
+
1007
+ const normalizeCommunicationMetricEntry = (entry: any) => {
1008
+ if (!entry) {
1009
+ return null;
1010
+ }
1011
+ const date = normalizeDate(entry.date || entry.timestamp);
1012
+ const type = normalizeString(entry.type);
1013
+ if (!date || !type) {
1014
+ return null;
1015
+ }
1016
+ const value = Number(entry.value ?? entry.count ?? 0);
1017
+ if (!Number.isFinite(value) || !value) {
1018
+ return null;
1019
+ }
1020
+ const id = normalizeIdentifier(entry._id) || normalizeIdentifier(entry.id) || '';
1021
+ const unit = normalizeString(entry.unit);
1022
+ const metadata = entry.metadata && typeof entry.metadata === 'object' ? entry.metadata : undefined;
1023
+
1024
+ return {
1025
+ _id: id || undefined,
1026
+ date,
1027
+ type,
1028
+ value,
1029
+ unit: unit || undefined,
1030
+ metadata
1031
+ };
1032
+ };
1033
+
1034
+ export function loadCronJobMethods(methodManager: MethodManager) {
1035
+ methodManager.methods({
1036
+ cronEmailMergedDocsCleanUp: {
1037
+ function: async function() {
1038
+ let files = await Files.find({$and: [{type: 'Email Merged Docs'}, {createdAt: {$lte: moment().subtract(1, 'day').toDate()}}]});
1039
+
1040
+ if (files.length) {
1041
+ await this.callMethod('deleteFiles', files.map(file => file.key));
1042
+ await Files.deleteMany({_id: {$in: files.map(a => a._id)}});
1043
+ }
1044
+
1045
+ return true;
1046
+ }
1047
+ },
1048
+ stuckCronJob: {
1049
+ function: async function() {
1050
+ let fiveMin = moment().subtract(5, 'minutes').toDate();
1051
+ let cronJobs = await CronJobs.find({running: true, updatedAt: {$lte: fiveMin}});
1052
+
1053
+ if (cronJobs.length) {
1054
+ await CronJobs.updateMany({_id: {$in: cronJobs.map(a => a._id)}}, {$set: {running: false}});
1055
+ }
1056
+
1057
+ return true;
1058
+ }
1059
+ },
1060
+ cronOpenAIUsageBillingSync: {
1061
+ check: new SimpleSchema({
1062
+ data: {
1063
+ type: Object,
1064
+ blackbox: true
1065
+ }
1066
+ }),
1067
+ function: async function() {
1068
+ const { config, endpoint, apiKey, batchSize, lookbackDays, overlapMinutes, enabled } = resolveOpenAIUsageSyncConfig();
1069
+ if (!enabled) {
1070
+ return true;
1071
+ }
1072
+
1073
+ const rootUrl = normalizeString(config['ROOT_URL']);
1074
+ if (rootUrl === 'https://resolveio.com' || rootUrl === 'http://localhost:4200') {
1075
+ return true;
1076
+ }
1077
+
1078
+ if (!endpoint || !apiKey) {
1079
+ console.warn('[AI Usage Sync] Missing endpoint or API key.');
1080
+ return false;
1081
+ }
1082
+
1083
+ const jobDoc = await CronJobs.findOne({
1084
+ name: {
1085
+ $in: [AI_USAGE_SYNC_JOB_NAME, LEGACY_AI_USAGE_SYNC_JOB_NAME]
1086
+ }
1087
+ });
1088
+ const runData = (jobDoc as any)?.method_run_data as any;
1089
+ const lastSyncedAt = normalizeDate(runData?.last_synced_at);
1090
+ const lastSyncedId = normalizeString(runData?.last_synced_id);
1091
+ const lookbackMs = lookbackDays * 24 * 60 * 60 * 1000;
1092
+ let cursorAt = lastSyncedAt || new Date(Date.now() - lookbackMs);
1093
+
1094
+ if (lastSyncedAt && overlapMinutes > 0) {
1095
+ cursorAt = new Date(cursorAt.getTime() - overlapMinutes * 60 * 1000);
1096
+ }
1097
+
1098
+ let cursorId = lastSyncedId;
1099
+ let totalSent = 0;
1100
+ let cursorMoved = false;
1101
+ const source = {
1102
+ clientSlug: ResolveIOServer.getClientName?.(),
1103
+ clientName: normalizeString(config['CLIENT_NAME']),
1104
+ rootUrl: rootUrl,
1105
+ reportedAt: new Date(),
1106
+ appVersion: process.env.APP_VERSION
1107
+ };
1108
+
1109
+ for (let i = 0; i < 1000; i++) {
1110
+ const query = buildOpenAIUsageQuery(cursorAt, cursorId);
1111
+ const entries = await OpenAIUsageLedger.find(query, {
1112
+ sort: { timestamp: 1, _id: 1 },
1113
+ limit: batchSize
1114
+ });
1115
+
1116
+ if (!entries.length) {
1117
+ break;
1118
+ }
1119
+
1120
+ const payloadEntries = entries
1121
+ .map(entry => normalizeUsagePayloadEntry(entry))
1122
+ .filter(Boolean);
1123
+
1124
+ if (payloadEntries.length) {
1125
+ await axios.post(endpoint, { entries: payloadEntries, source }, {
1126
+ headers: {
1127
+ 'Content-Type': 'application/json',
1128
+ 'X-ResolveIO-OpenAI-Usage-Key': apiKey,
1129
+ 'X-OpenAI-Usage-Key': apiKey,
1130
+ 'X-API-Key': apiKey
1131
+ },
1132
+ timeout: 20000
1133
+ });
1134
+ totalSent += payloadEntries.length;
1135
+ }
1136
+
1137
+ const lastEntry = entries[entries.length - 1];
1138
+ if (lastEntry) {
1139
+ cursorAt = normalizeDate(lastEntry.timestamp) || cursorAt;
1140
+ cursorId = normalizeString(lastEntry._id) || cursorId;
1141
+ cursorMoved = true;
1142
+ }
1143
+
1144
+ if (entries.length < batchSize) {
1145
+ break;
1146
+ }
1147
+ }
1148
+
1149
+ if (cursorMoved) {
1150
+ const jobSelector = (jobDoc as any)?._id
1151
+ ? { _id: (jobDoc as any)._id }
1152
+ : { name: AI_USAGE_SYNC_JOB_NAME };
1153
+ await CronJobs.updateOne(
1154
+ jobSelector,
1155
+ {
1156
+ $set: {
1157
+ name: AI_USAGE_SYNC_JOB_NAME,
1158
+ 'method_run_data.last_synced_at': cursorAt,
1159
+ 'method_run_data.last_synced_id': cursorId,
1160
+ 'method_run_data.last_sent_at': new Date(),
1161
+ 'method_run_data.last_sent_count': totalSent
1162
+ }
1163
+ }
1164
+ );
1165
+ }
1166
+
1167
+ return true;
1168
+ }
1169
+ },
1170
+ cronCommunicationMetricsSync: {
1171
+ check: new SimpleSchema({
1172
+ data: {
1173
+ type: Object,
1174
+ blackbox: true
1175
+ }
1176
+ }),
1177
+ function: async function() {
1178
+ const { config, endpoint, apiKey, batchSize, lookbackDays, overlapMinutes, enabled } = resolveCommunicationMetricsSyncConfig();
1179
+ if (!enabled) {
1180
+ return true;
1181
+ }
1182
+
1183
+ const rootUrl = normalizeString(config['ROOT_URL']);
1184
+ if (rootUrl === 'https://resolveio.com' || rootUrl === 'http://localhost:4200') {
1185
+ return true;
1186
+ }
1187
+
1188
+ if (!endpoint || !apiKey) {
1189
+ console.warn('[Communication Metrics Sync] Missing endpoint or API key.');
1190
+ return false;
1191
+ }
1192
+
1193
+ const jobDoc = await CronJobs.findOne({ name: 'Communication Metrics Sync' });
1194
+ const runData = (jobDoc as any)?.method_run_data as any;
1195
+ const lastSyncedAt = normalizeDate(runData?.last_synced_at);
1196
+ const lastSyncedId = normalizeString(runData?.last_synced_id);
1197
+ const lookbackMs = lookbackDays * 24 * 60 * 60 * 1000;
1198
+ let cursorAt = lastSyncedAt || new Date(Date.now() - lookbackMs);
1199
+
1200
+ if (lastSyncedAt && overlapMinutes > 0) {
1201
+ cursorAt = new Date(cursorAt.getTime() - overlapMinutes * 60 * 1000);
1202
+ }
1203
+
1204
+ let cursorId = lastSyncedId;
1205
+ let totalSent = 0;
1206
+ let cursorMoved = false;
1207
+ const source = {
1208
+ clientSlug: ResolveIOServer.getClientName?.(),
1209
+ clientName: normalizeString(config['CLIENT_NAME']),
1210
+ rootUrl: rootUrl,
1211
+ reportedAt: new Date(),
1212
+ appVersion: process.env.APP_VERSION
1213
+ };
1214
+
1215
+ for (let i = 0; i < 1000; i++) {
1216
+ const query = buildCommunicationMetricsQuery(cursorAt, cursorId);
1217
+ const entries = await CommunicationMetrics.find(query, {
1218
+ sort: { date: 1, _id: 1 },
1219
+ limit: batchSize
1220
+ });
1221
+
1222
+ if (!entries.length) {
1223
+ break;
1224
+ }
1225
+
1226
+ const payloadEntries = entries
1227
+ .map(entry => normalizeCommunicationMetricEntry(entry))
1228
+ .filter(Boolean);
1229
+
1230
+ if (payloadEntries.length) {
1231
+ await axios.post(endpoint, { entries: payloadEntries, source }, {
1232
+ headers: {
1233
+ 'Content-Type': 'application/json',
1234
+ 'X-ResolveIO-Communication-Metrics-Key': apiKey,
1235
+ 'X-ResolveIO-OpenAI-Usage-Key': apiKey,
1236
+ 'X-OpenAI-Usage-Key': apiKey,
1237
+ 'X-API-Key': apiKey
1238
+ },
1239
+ timeout: 20000
1240
+ });
1241
+ totalSent += payloadEntries.length;
1242
+ }
1243
+
1244
+ const lastEntry = entries[entries.length - 1];
1245
+ if (lastEntry) {
1246
+ cursorAt = normalizeDate(lastEntry.date) || cursorAt;
1247
+ cursorId = normalizeString((lastEntry as any)._id) || cursorId;
1248
+ cursorMoved = true;
1249
+ }
1250
+
1251
+ if (entries.length < batchSize) {
1252
+ break;
1253
+ }
1254
+ }
1255
+
1256
+ if (cursorMoved) {
1257
+ await CronJobs.updateOne(
1258
+ { name: 'Communication Metrics Sync' },
1259
+ {
1260
+ $set: {
1261
+ 'method_run_data.last_synced_at': cursorAt,
1262
+ 'method_run_data.last_synced_id': cursorId,
1263
+ 'method_run_data.last_sent_at': new Date(),
1264
+ 'method_run_data.last_sent_count': totalSent
1265
+ }
1266
+ }
1267
+ );
1268
+ }
1269
+
1270
+ return true;
1271
+ }
1272
+ },
1273
+ reportbuilderCronJob: {
1274
+ check: new SimpleSchema({
1275
+ data: {
1276
+ type: Object,
1277
+ blackbox: true
1278
+ }
1279
+ }),
1280
+ function: async function(data: Object) {
1281
+ if (data && data['active'] === false) {
1282
+ return true;
1283
+ }
1284
+
1285
+ let report = await ReportBuilderReports.findById(data['id_report']);
1286
+
1287
+ try {
1288
+ const layoutColumns = getLayoutColumnsFromReport(report);
1289
+ const selectedFields = syncSelectedFieldsFromLayout(report, layoutColumns);
1290
+ report.fields_selected = selectedFields;
1291
+ report.fields_layout = layoutColumns;
1292
+
1293
+ (report.fields_selected || []).forEach((field: any, index: number) => {
1294
+ if (!field?.id) {
1295
+ field.id = `f_${index}`;
1296
+ }
1297
+ });
1298
+
1299
+ (report.fields_custom || []).forEach(custom => {
1300
+ if (!custom) {
1301
+ return;
1302
+ }
1303
+
1304
+ if (!custom.selFieldId && custom.id) {
1305
+ const match = (report.fields_selected || []).find(field => field.fieldPath === custom.id);
1306
+ if (match?.id) {
1307
+ custom.selFieldId = match.id;
1308
+ }
1309
+ }
1310
+ });
1311
+
1312
+ report.fields_selected.forEach((field: any) => {
1313
+ if (!field?.fieldPath) {
1314
+ return;
1315
+ }
1316
+
1317
+ let dotPath = field.fieldPath.replace(/\.\$/g, '');
1318
+ let fieldData = dotPath.split('.');
1319
+ field.fieldPathObj = null;
1320
+ for (let i = fieldData.length - 1; i >= 0; i--) {
1321
+ if (!field.fieldPathObj) {
1322
+ field.fieldPathObj = {[fieldData[i]]: 1};
1323
+ }
1324
+ else {
1325
+ field.fieldPathObj = {[fieldData[i]]: field.fieldPathObj};
1326
+ }
1327
+ }
1328
+ });
1329
+
1330
+ if ((report.fields_selected && report.fields_selected.length) || (report.groups_row && report.groups_row.length)) {
1331
+ let fieldsObj = {};
1332
+ const selectedFieldsForQuery = (report.fields_selected || []).filter(field => !((field?.fieldPath || '').startsWith('c_')));
1333
+
1334
+ report.fields_sort = report.fields_sort || [];
1335
+
1336
+ selectedFieldsForQuery.forEach((field: any) => {
1337
+ if (field.collection_name === report.collection_root && field.fieldPathObj) {
1338
+ fieldsObj = mergeDeep(fieldsObj, field.fieldPathObj);
1339
+ }
1340
+ });
1341
+
1342
+ const groupIds = (report.groups_row || []).map(g => g.id).filter(Boolean);
1343
+ const userSorts = (report.fields_sort || []).filter(s => !!s.field);
1344
+ const userSortByField = {};
1345
+ userSorts.forEach(s => {
1346
+ userSortByField[s.field] = s;
1347
+ });
1348
+
1349
+ const groupSorts = groupIds.map(id => userSortByField[id] || {field: id, order: 'asc'});
1350
+ const nonGroupSorts = userSorts.filter(s => !groupIds.includes(s.field));
1351
+ const effectiveSorts = groupIds.length ? [...groupSorts, ...nonGroupSorts] : userSorts;
1352
+
1353
+ let sortObj = null;
1354
+ if (effectiveSorts.length) {
1355
+ sortObj = {};
1356
+ effectiveSorts.forEach(sortField => {
1357
+ let targetField = sortField.field;
1358
+
1359
+ if (!targetField) {
1360
+ return;
1361
+ }
1362
+
1363
+ if (targetField.startsWith('layout_col_')) {
1364
+ const resolved = resolveFieldRefToLeafId(targetField, report);
1365
+ targetField = resolved || targetField;
1366
+ }
1367
+
1368
+ if (targetField.startsWith('gr_')) {
1369
+ sortObj['_id.' + targetField] = (sortField.order === 'asc' ? 1 : -1);
1370
+ }
1371
+ else {
1372
+ sortObj[targetField] = (sortField.order === 'asc' ? 1 : -1);
1373
+ }
1374
+ });
1375
+ }
1376
+
1377
+ const totalsForQuery = (report.fields_total || []).map(total => {
1378
+ const mappedFields = (total.fields || [])
1379
+ .map(ref => {
1380
+ if (!ref) {
1381
+ return null;
1382
+ }
1383
+
1384
+ if (ref.startsWith('layout_col_')) {
1385
+ return resolveFieldRefToLeafId(ref, report);
1386
+ }
1387
+
1388
+ return ref;
1389
+ })
1390
+ .filter(f => !!f);
1391
+
1392
+ return {...total, fields: mappedFields};
1393
+ }).filter(total => (total.fields || []).length);
1394
+
1395
+ report.fields_total = totalsForQuery;
1396
+
1397
+ let rootOptions: PaginationOptions = {
1398
+ limit: 0,
1399
+ skip: 0,
1400
+ fields: fieldsObj,
1401
+ sort: sortObj
1402
+ };
1403
+
1404
+ let filters = [];
1405
+ let filterArrayFields = [];
1406
+ let filterArrays = [];
1407
+
1408
+ selectedFieldsForQuery
1409
+ .filter(f => (f.fieldPath || '').includes('.$.'))
1410
+ .forEach(sel => {
1411
+ if (!filterArrayFields.some(f => f?.fieldPath === sel.fieldPath)) {
1412
+ filterArrayFields.push(sel);
1413
+ }
1414
+ });
1415
+
1416
+ (report.fields_filter || []).forEach(filterAnd => {
1417
+ let ors = [];
1418
+ let hasArrayTarget = false;
1419
+
1420
+ (filterAnd.ors || []).forEach((filter: any) => {
1421
+ const targets = getFilterTargets(filter.field, report);
1422
+
1423
+ if (!targets.length) {
1424
+ return;
1425
+ }
1426
+
1427
+ targets.forEach(path => {
1428
+ const isArrayTarget = path.includes('.$.');
1429
+ const normalizedPath = path.replace(/\.\$/g, '');
1430
+ const expr = buildFilterExpression(filter, normalizedPath);
1431
+
1432
+ if (expr) {
1433
+ ors.push(expr);
1434
+ }
1435
+
1436
+ if (isArrayTarget) {
1437
+ hasArrayTarget = true;
1438
+ const leaf = selectedFieldsForQuery.find(a => a.fieldPath === path);
1439
+ if (leaf && !filterArrayFields.some(f => f?.fieldPath === leaf.fieldPath)) {
1440
+ filterArrayFields.push(leaf);
1441
+ }
1442
+ }
1443
+ });
1444
+ });
1445
+
1446
+ if (ors.length) {
1447
+ const filterObj = {$or: ors};
1448
+ if (hasArrayTarget) {
1449
+ filterArrays.push(filterObj);
1450
+ }
1451
+ else {
1452
+ filters.push(filterObj);
1453
+ }
1454
+ }
1455
+ });
1456
+
1457
+ const res = await this.callMethod('reportBuilderGetResults', report.type, report.collection_root, rootOptions, filters, filterArrays, filterArrayFields, selectedFieldsForQuery, report.fields_custom, report.groups_row, totalsForQuery, report.fields_link, report.id_date_field || null, report.date_interval || null, report.group_type);
1458
+
1459
+ if (res && res[0]) {
1460
+ let results = res[0].results;
1461
+ let reportTotals = res[0].totals;
1462
+
1463
+ if (report.type === 'Group') {
1464
+ applyClientGroupSortToData(results, report, effectiveSorts);
1465
+ }
1466
+
1467
+ const customFields = (report.fields_custom || []).map(cust => ({
1468
+ ...cust,
1469
+ id: cust.selFieldId,
1470
+ fieldType: 'Number',
1471
+ leafValueType: cust.leafValueType || 'Value',
1472
+ leafFormatType: 'Number',
1473
+ columnName: cust.columnName || cust.id,
1474
+ show: true
1475
+ }));
1476
+
1477
+ const exportFields = report.fields_selected.filter(a => a.show).concat((<any>customFields));
1478
+
1479
+ if (report.type === 'List') {
1480
+ exportFields.forEach(field => {
1481
+ if ((field.fieldType === 'Number' || field.leafValueType === 'Count') && field.leafFormatType === 'Number') {
1482
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined ).forEach(result => {
1483
+ result[field.id] = result[field.id].toLocaleString();
1484
+ });
1485
+ }
1486
+ else if ((field.fieldType === 'Number' || field.leafValueType === 'Count') && field.leafFormatType === 'String') {
1487
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1488
+ result[field.id] = result[field.id].toLocaleString('en-US', { style: 'currency', currency: 'USD' });
1489
+ });
1490
+ }
1491
+ else if ((field.fieldType === 'Number' || field.leafValueType === 'Count') && field.leafFormatType === 'Currency') {
1492
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1493
+ result[field.id] = round(result[field.id], 2).toLocaleString('en-US', { style: 'currency', currency: 'USD' });
1494
+ });
1495
+ }
1496
+ else if (field.fieldType === 'Boolean' && (!field.leafFormatType || field.leafFormatType === 'Boolean')) {
1497
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1498
+ result[field.id] = result[field.id] === true ? 'True' : 'False';
1499
+ });
1500
+ }
1501
+ else if (field.fieldType === 'Boolean' && field.leafFormatType === 'String') {
1502
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1503
+ result[field.id] = result[field.id] === true ? 'Yes' : 'No';
1504
+ });
1505
+ }
1506
+ else if (field.fieldType === 'Boolean' && field.leafFormatType === 'Boolean_Number') {
1507
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1508
+ result[field.id] = result[field.id] === true ? 1 : 0;
1509
+ });
1510
+ }
1511
+ else if (field.fieldType === 'Date' && (field.leafFormatType === 'Date')) {
1512
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1513
+ result[field.id] = moment(result[field.id]).format('L');
1514
+ });
1515
+ }
1516
+ else if (field.fieldType === 'Date' && field.leafFormatType === 'Time') {
1517
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1518
+ result[field.id] = moment(result[field.id]).format('LT');
1519
+ });
1520
+ }
1521
+ else if (field.fieldType === 'Date' && field.leafFormatType === 'DateTime') {
1522
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1523
+ result[field.id] = moment(result[field.id]).format('MM/DD/YYYY h:mm A');
1524
+ });
1525
+ }
1526
+ else if (field.fieldType === 'Date' && field.leafFormatType === 'Date_long') {
1527
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1528
+ result[field.id] = moment(result[field.id]).format('LL');
1529
+ });
1530
+ }
1531
+ else if (field.fieldType === 'Date' && field.leafFormatType === 'DateTime_long') {
1532
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1533
+ result[field.id] = moment(result[field.id]).format('LLL');
1534
+ });
1535
+ }
1536
+ else if (field.fieldType === 'Date' && field.leafFormatType === 'Timestamp') {
1537
+ results.filter(a => a[field.id] !== null && a[field.id] !== undefined).forEach(result => {
1538
+ result[field.id] = moment(result[field.id]).toDate().getTime();
1539
+ });
1540
+ }
1541
+ });
1542
+
1543
+ let wb = new Excel.Workbook();
1544
+ let ws = wb.addWorksheet('RB_1', {views: [{showGridLines: true}]});
1545
+ wb.properties.date1904 = false;
1546
+ wb.calcProperties.fullCalcOnLoad = true;
1547
+
1548
+ let currentRow = 1;
1549
+ let row = ws.getRow(currentRow);
1550
+
1551
+ row.getCell(2).font = {
1552
+ name: 'Arial',
1553
+ bold: true,
1554
+ size: 14
1555
+ };
1556
+ row.getCell(4).font = {
1557
+ name: 'Arial',
1558
+ size: 14
1559
+ };
1560
+ row.getCell(2).value = 'Report Name:';
1561
+ row.getCell(4).value = report.report_name;
1562
+
1563
+ currentRow += 1;
1564
+ row = ws.getRow(currentRow);
1565
+
1566
+ row.getCell(2).font = {
1567
+ name: 'Arial',
1568
+ bold: true,
1569
+ size: 14
1570
+ };
1571
+ row.getCell(4).font = {
1572
+ name: 'Arial',
1573
+ size: 14
1574
+ };
1575
+ row.getCell(2).value = 'Run Date:';
1576
+ row.getCell(4).value = moment().format('LLL');
1577
+
1578
+ currentRow += 2;
1579
+ row = ws.getRow(currentRow);
1580
+
1581
+ const useLayout = (layoutColumns && layoutColumns.length > 0);
1582
+ const headers: string[] = [];
1583
+
1584
+ report.groups_row.forEach(group => {
1585
+ headers.push(group.columnName);
1586
+ });
1587
+
1588
+ if (useLayout) {
1589
+ layoutColumns.forEach((col, idx) => {
1590
+ headers.push(col.header || `Column ${idx + 1}`);
1591
+ });
1592
+ }
1593
+ else {
1594
+ exportFields.forEach(field => {
1595
+ headers.push(field.columnName);
1596
+ });
1597
+ }
1598
+
1599
+ headers.forEach((header, headerIdx) => {
1600
+ row.getCell(headerIdx + 1).value = header;
1601
+ });
1602
+
1603
+ currentRow += 1;
1604
+ row = ws.getRow(currentRow);
1605
+
1606
+ const exportResults = deepCopy(results || []);
1607
+
1608
+ exportResults.forEach(result => {
1609
+ let currentCol = 1;
1610
+
1611
+ report.groups_row.forEach(group => {
1612
+ writeExcelCell(row.getCell(currentCol), result?._id?.[group.id] ?? '', group.treeItem);
1613
+ currentCol += 1;
1614
+ });
1615
+
1616
+ if (useLayout) {
1617
+ const layoutRow = materializeLayoutRow(report, layoutColumns, report.fields_selected, result);
1618
+ layoutColumns.forEach((col, colIdx) => {
1619
+ const cellValue = getLayoutCellExportValue(report, layoutRow, colIdx);
1620
+ const primaryFieldMeta = getPrimaryFieldForLayoutColumn(col, report.fields_selected);
1621
+ writeExcelCell(row.getCell(currentCol), cellValue === undefined ? '' : cellValue, primaryFieldMeta);
1622
+ currentCol += 1;
1623
+ });
1624
+ }
1625
+ else {
1626
+ exportFields.forEach(field => {
1627
+ writeExcelCell(row.getCell(currentCol), result[field.id] ?? '', field);
1628
+ currentCol += 1;
1629
+ });
1630
+ }
1631
+
1632
+ currentRow += 1;
1633
+ row = ws.getRow(currentRow);
1634
+ });
1635
+
1636
+ try {
1637
+ let buffer = await wb.xlsx.writeBuffer();
1638
+ for (let email of data['emails']) {
1639
+ await this.sendEmail(email,
1640
+ this.serverConfig['CLIENT_NAME'] + ' Scheduled Report - ' + report.report_name,
1641
+ '',
1642
+ `
1643
+ <b>` + this.serverConfig['CLIENT_NAME'] + ` Automated Report</b><br>
1644
+ <b>Report Name: </b>` + report.report_name + `<br>
1645
+ <b>Prepared By: </b>` + data['user'] + `<br><br>
1646
+ Attached are the results of this automated report.<br><br>
1647
+ ResolveIO<br><br>`,
1648
+ [
1649
+ {
1650
+ filename: report.report_name + '-' + moment().format('MM-DD-YYYY-hh-mm-A') + '.xlsx',
1651
+ content: buffer
1652
+ }
1653
+ ],
1654
+ this.serverConfig['MAIL_FROM_REPORTS'],
1655
+ ''
1656
+ );
1657
+ }
1658
+ }
1659
+ catch (err) {
1660
+ console.log('Error writing excel export', err);
1661
+ }
1662
+ }
1663
+ else if (report.type === 'Group') {
1664
+ exportFields.forEach(field => {
1665
+ results.forEach(result => {
1666
+ modifyDataTypeField(report, result, field, 1);
1667
+ });
1668
+ });
1669
+
1670
+ let wb = new Excel.Workbook();
1671
+ let ws = wb.addWorksheet('RB_1', {views: [{showGridLines: false}]});
1672
+ wb.properties.date1904 = false;
1673
+ wb.calcProperties.fullCalcOnLoad = true;
1674
+
1675
+ let currentRow = 1;
1676
+ let row = ws.getRow(currentRow);
1677
+
1678
+ row.getCell(2).font = {
1679
+ name: 'Arial',
1680
+ bold: true,
1681
+ size: 14
1682
+ };
1683
+ row.getCell(4).font = {
1684
+ name: 'Arial',
1685
+ size: 14
1686
+ };
1687
+ row.getCell(2).value = 'Report Name:';
1688
+ row.getCell(4).value = report.report_name;
1689
+
1690
+ currentRow += 1;
1691
+ row = ws.getRow(currentRow);
1692
+
1693
+ row.getCell(2).font = {
1694
+ name: 'Arial',
1695
+ bold: true,
1696
+ size: 14
1697
+ };
1698
+ row.getCell(4).font = {
1699
+ name: 'Arial',
1700
+ size: 14
1701
+ };
1702
+ row.getCell(2).value = 'Run Date:';
1703
+ row.getCell(4).value = moment().format('LLL');
1704
+
1705
+ currentRow += 2;
1706
+
1707
+ let copy = deepCopy(results);
1708
+ let widths = [];
1709
+ copy.forEach(res => {
1710
+ currentRow = tabGroupExcelRecursive(report, ws, currentRow, 1, res, exportFields);
1711
+ tabGroupExcelWidthRecursive(report, 1, res, widths, exportFields);
1712
+ });
1713
+
1714
+ for (let i = 1; i < ws.columns.length; i++) {
1715
+ ws.columns[i].width = widths[i - 1];
1716
+ }
1717
+
1718
+ let now = new Date();
1719
+
1720
+ try {
1721
+ let buffer = await wb.xlsx.writeBuffer();
1722
+ for (let email of data['emails']) {
1723
+ await this.sendEmail(email,
1724
+ this.serverConfig['CLIENT_NAME'] + ' Scheduled Report - ' + report.report_name,
1725
+ '',
1726
+ `
1727
+ <b>` + this.serverConfig['CLIENT_NAME'] + ` Automated Report</b><br>
1728
+ <b>Report Name: </b>` + report.report_name + `<br>
1729
+ <b>Prepared By: </b>` + data['user'] + `<br><br>
1730
+ Attached are the results of this automated report.<br><br>
1731
+ Have a great day!<br><br>
1732
+ ResolveIO<br><br>`,
1733
+ [
1734
+ {
1735
+ filename: 'Group_Report_' + report.report_name + '_' + now.getFullYear() + '_' + (now.getMonth() + 1) + '_' + now.getDate() + '.xlsx',
1736
+ content: buffer
1737
+ }
1738
+ ],
1739
+ this.serverConfig['MAIL_FROM_REPORTS'],
1740
+ ''
1741
+ );
1742
+ }
1743
+ }
1744
+ catch (err) {
1745
+ console.log('Error writing excel export', err)
1746
+ };
1747
+ }
1748
+ else if (report.type === 'Dated') {
1749
+ let datedUniqueDates = [];
1750
+ let datedUniqueGroups = [];
1751
+ let datedData = [];
1752
+ let reportTotalGroups = [];
1753
+ let reportTotalDates = [];
1754
+
1755
+ // Step 1: Handle quarterly dates if applicable
1756
+ if (report.date_interval === 'Quarterly') {
1757
+ results.forEach(dataPt => {
1758
+ if (dataPt._id.month < 4) {
1759
+ dataPt._id.quarter = 1;
1760
+ }
1761
+ else if (dataPt._id.month < 7) {
1762
+ dataPt._id.quarter = 2;
1763
+ }
1764
+ else if (dataPt._id.month < 10) {
1765
+ dataPt._id.quarter = 3;
1766
+ }
1767
+ else {
1768
+ dataPt._id.quarter = 4;
1769
+ }
1770
+
1771
+ delete dataPt._id.month;
1772
+ });
1773
+
1774
+ // Deduplicate quarterly data
1775
+ for (let i = results.length - 1; i >= 1; i--) {
1776
+ let dataPt = results[i];
1777
+ if (results.filter(a => JSON.stringify(a._id) === JSON.stringify(dataPt._id)).length > 1) {
1778
+ let tmpData = results.find(a => JSON.stringify(a._id) === JSON.stringify(dataPt._id));
1779
+
1780
+ Object.keys(tmpData)
1781
+ .filter(a => a !== '_id')
1782
+ .forEach(key => {
1783
+ tmpData[key] += dataPt[key];
1784
+ });
1785
+
1786
+ results.splice(i, 1); // Remove the duplicate entry
1787
+ }
1788
+ }
1789
+ }
1790
+
1791
+ // Step 2: Prepare unique groups
1792
+ let tmpResults = deepCopy(results);
1793
+ tmpResults.forEach(result => {
1794
+ delete result._id.day;
1795
+ delete result._id.week;
1796
+ delete result._id.month;
1797
+ delete result._id.quarter;
1798
+ delete result._id.year;
1799
+
1800
+ let tmpGroup = {};
1801
+ Object.keys(result._id).forEach(key => {
1802
+ tmpGroup[key] = result._id[key];
1803
+ });
1804
+
1805
+ if (!datedUniqueGroups.find(a => JSON.stringify(a) === JSON.stringify(tmpGroup))) {
1806
+ datedUniqueGroups.push(tmpGroup);
1807
+ }
1808
+ });
1809
+
1810
+ // Step 3: Prepare unique dates
1811
+ tmpResults = deepCopy(results);
1812
+ tmpResults.forEach(result => {
1813
+ Object.keys(result._id)
1814
+ .filter(a => a !== 'second' && a !== 'hour' && a !== 'minute' && a !== 'day' && a !== 'week' && a !== 'month' && a !== 'quarter' && a !== 'year')
1815
+ .forEach(key => {
1816
+ delete result._id[key];
1817
+ });
1818
+
1819
+ let tmpDate = {};
1820
+ Object.keys(result._id).forEach(key => {
1821
+ tmpDate[key] = result._id[key];
1822
+ });
1823
+
1824
+ if (!datedUniqueDates.find(a => JSON.stringify(a) === JSON.stringify(tmpDate))) {
1825
+ datedUniqueDates.push(tmpDate);
1826
+ }
1827
+ });
1828
+
1829
+ // Build the date strings
1830
+ // Ensure moment is using America/Chicago timezone for all date processing
1831
+ datedUniqueDates.forEach(result => {
1832
+ if (!result.year) {
1833
+ result.dateString = 'No Date';
1834
+ }
1835
+ else {
1836
+ if (report.date_interval === 'Seconds') {
1837
+ result.date = moment.tz({
1838
+ year: result.year,
1839
+ month: result.month - 1, // Use `month - 1` since months are zero-indexed in moment as well
1840
+ day: result.day,
1841
+ hour: result.hour,
1842
+ minute: result.minute,
1843
+ second: result.second
1844
+ }, (process.env.TZ_CLIENT || 'America/Chicago')).toDate();
1845
+ result.dateString = moment(result.date).format('MMM DD YYYY HH:mm:ss');
1846
+ }
1847
+ else if (report.date_interval === 'Minutes') {
1848
+ result.date = moment.tz({
1849
+ year: result.year,
1850
+ month: result.month - 1,
1851
+ day: result.day,
1852
+ hour: result.hour,
1853
+ minute: result.minute
1854
+ }, (process.env.TZ_CLIENT || 'America/Chicago')).toDate();
1855
+ result.dateString = moment(result.date).format('MMM DD YYYY HH:mm');
1856
+ }
1857
+ else if (report.date_interval === 'Hours') {
1858
+ result.date = moment.tz({
1859
+ year: result.year,
1860
+ month: result.month - 1,
1861
+ day: result.day,
1862
+ hour: result.hour
1863
+ }, (process.env.TZ_CLIENT || 'America/Chicago')).toDate();
1864
+ result.dateString = moment(result.date).format('MMM DD YYYY HH');
1865
+ }
1866
+ else if (report.date_interval === 'Daily') {
1867
+ result.date = moment.tz({
1868
+ year: result.year,
1869
+ month: result.month - 1,
1870
+ day: result.day
1871
+ }, (process.env.TZ_CLIENT || 'America/Chicago')).toDate();
1872
+ result.dateString = moment(result.date).format('MMM DD YYYY');
1873
+ }
1874
+ else if (report.date_interval === 'Weekly') {
1875
+ result.date = moment.tz({
1876
+ year: result.year,
1877
+ week: result.week
1878
+ }, (process.env.TZ_CLIENT || 'America/Chicago')).day('Monday').toDate();
1879
+ result.dateLast = moment.tz({
1880
+ year: result.year,
1881
+ week: result.week
1882
+ }, (process.env.TZ_CLIENT || 'America/Chicago')).day('Sunday').toDate();
1883
+ result.dateString = 'Week #' + result.week + ' ' + result.year + ' (' + moment(result.date).format('MM/DD/YY') + ' - ' + moment(result.dateLast).format('MM/DD/YY') + ')';
1884
+ }
1885
+ else if (report.date_interval === 'Monthly') {
1886
+ result.date = moment.tz({
1887
+ year: result.year,
1888
+ month: result.month - 1, // Months are zero-indexed in moment
1889
+ }, (process.env.TZ_CLIENT || 'America/Chicago')).toDate();
1890
+ result.dateString = moment(result.date).format('MMM YYYY');
1891
+ }
1892
+ else if (report.date_interval === 'Quarterly') {
1893
+ result.date = moment.tz({
1894
+ year: result.year
1895
+ }, (process.env.TZ_CLIENT || 'America/Chicago')).quarter(result.quarter).toDate();;
1896
+ result.dateString = 'Q' + result.quarter + ' ' + result.year;
1897
+ }
1898
+ else if (report.date_interval === 'Yearly') {
1899
+ result.date = moment.tz({
1900
+ year: result.year
1901
+ }, (process.env.TZ_CLIENT || 'America/Chicago')).toDate();
1902
+ result.dateString = result.year.toString();
1903
+ }
1904
+ }
1905
+ });
1906
+
1907
+ datedUniqueDates = datedUniqueDates.filter(a => !a.date)
1908
+ .concat(datedUniqueDates.filter(a => a.date).sort((a, b) => a.date.getTime() - b.date.getTime()));
1909
+
1910
+ // Step 4: Build datedData and reportTotalGroups
1911
+ datedUniqueDates.forEach(date => {
1912
+ let tmpData = [];
1913
+ let tmpTotalDated = {};
1914
+ let tmpTotalCnt = {};
1915
+ let tmpTotalType = {};
1916
+
1917
+ datedUniqueGroups.forEach(group => {
1918
+ let result = results.find(a => {
1919
+ let match = true;
1920
+
1921
+ // Match the group fields
1922
+ Object.keys(group).forEach(key => {
1923
+ if (a._id[key] !== group[key]) {
1924
+ match = false;
1925
+ }
1926
+ });
1927
+
1928
+ // Match the date fields
1929
+ Object.keys(date).filter(key => key !== 'date' && key !== 'dateLast' && key !== 'dateString').forEach(key => {
1930
+ if (a._id[key] !== date[key]) {
1931
+ match = false;
1932
+ }
1933
+ });
1934
+
1935
+ return match;
1936
+ });
1937
+
1938
+ if (result) {
1939
+ tmpData.push(result);
1940
+
1941
+ Object.keys(result._id).filter(a => a).forEach(key => {
1942
+ if (report.fields_total.filter(a => a.fields.includes(key)).length) {
1943
+ if (!tmpTotalDated[key]) {
1944
+ tmpTotalDated[key] = result._id[key];
1945
+ }
1946
+ else {
1947
+ tmpTotalDated[key] += result._id[key];
1948
+ }
1949
+
1950
+ if (!tmpTotalCnt[key]) {
1951
+ tmpTotalCnt[key] = 1;
1952
+ }
1953
+ else {
1954
+ tmpTotalCnt[key] += 1;
1955
+ }
1956
+
1957
+ tmpTotalType[key] = report.fields_total.find(a => a.fields.includes(key)).type;
1958
+ }
1959
+ });
1960
+
1961
+ Object.keys(result).filter(a => a && a !== '_id').forEach(key => {
1962
+ if (report.fields_total.filter(a => a.fields.includes(key)).length) {
1963
+ if (!tmpTotalDated[key]) {
1964
+ tmpTotalDated[key] = result[key];
1965
+ }
1966
+ else {
1967
+ tmpTotalDated[key] += result[key];
1968
+ }
1969
+
1970
+ if (!tmpTotalCnt[key]) {
1971
+ tmpTotalCnt[key] = 1;
1972
+ }
1973
+ else {
1974
+ tmpTotalCnt[key] += 1;
1975
+ }
1976
+
1977
+ tmpTotalType[key] = report.fields_total.find(a => a.fields.includes(key)).type;
1978
+ }
1979
+ });
1980
+ }
1981
+ else {
1982
+ tmpData.push(null);
1983
+ }
1984
+ });
1985
+
1986
+ // Handle averaging for totals
1987
+ Object.keys(tmpTotalType).forEach(totalKey => {
1988
+ if (tmpTotalType[totalKey] === 'avg') {
1989
+ tmpTotalDated[totalKey] = tmpTotalDated[totalKey] / tmpTotalCnt[totalKey];
1990
+ }
1991
+
1992
+ tmpTotalDated[totalKey] = round(tmpTotalDated[totalKey], 2);
1993
+ });
1994
+
1995
+ reportTotalDates.push(tmpTotalDated);
1996
+ datedData.push(tmpData);
1997
+ });
1998
+
1999
+ // Calculate group-level totals
2000
+ datedUniqueGroups.forEach(group => {
2001
+ let tmpTotalGroup = {};
2002
+ let filteredData = results.filter(a => {
2003
+ let match = (!Object.keys(group).length || group._id === null) ? false : true;
2004
+ Object.keys(group).filter(a => a !== 'date' && a !== 'dateLast' && a !== 'dateString' && a !== 'second' && a !== 'minute' && a !== 'hour' && a !== 'day' && a !== 'week' && a !== 'month' && a !== 'quarter' && a !== 'year').forEach(key => {
2005
+ if (a._id[key] !== group[key]) {
2006
+ match = false;
2007
+ }
2008
+ });
2009
+ return match;
2010
+ });
2011
+
2012
+ report.fields_total.forEach(total => {
2013
+ total.fields.forEach(field => {
2014
+ if (filteredData.length) {
2015
+ tmpTotalGroup[field] = filteredData.filter(a => a && a[field])
2016
+ .map(a => a[field])
2017
+ .reduce((a, b) => a + b, 0);
2018
+
2019
+ if (total.type === 'avg') {
2020
+ tmpTotalGroup[field] = round(tmpTotalGroup[field] / filteredData.length, 2);
2021
+ }
2022
+ }
2023
+ else {
2024
+ tmpTotalGroup[field] = 0;
2025
+ }
2026
+ });
2027
+ });
2028
+
2029
+ reportTotalGroups.push(tmpTotalGroup);
2030
+ });
2031
+
2032
+ // Step 5: Proceed with Excel generation using datedData and reportTotalGroups
2033
+ let wb = new Excel.Workbook();
2034
+ let ws = wb.addWorksheet('Dated_Report', {views: [{showGridLines: true}]});
2035
+ wb.properties.date1904 = false;
2036
+ wb.calcProperties.fullCalcOnLoad = true;
2037
+
2038
+ let currentRow = 1;
2039
+ let currentCol = 1;
2040
+ let row = ws.getRow(currentRow);
2041
+
2042
+ row.getCell(2).font = {
2043
+ name: 'Arial',
2044
+ bold: true,
2045
+ size: 14
2046
+ };
2047
+ row.getCell(4).font = {
2048
+ name: 'Arial',
2049
+ size: 14
2050
+ };
2051
+ row.getCell(2).value = 'Report Name:';
2052
+ row.getCell(4).value = report.report_name;
2053
+
2054
+ currentRow += 1;
2055
+ row = ws.getRow(currentRow);
2056
+
2057
+ row.getCell(2).font = {
2058
+ name: 'Arial',
2059
+ bold: true,
2060
+ size: 14
2061
+ };
2062
+ row.getCell(4).font = {
2063
+ name: 'Arial',
2064
+ size: 14
2065
+ };
2066
+ row.getCell(2).value = 'Run Date:';
2067
+ row.getCell(4).value = moment().format('LLL');
2068
+
2069
+ currentRow += 2;
2070
+ row = ws.getRow(currentRow);
2071
+
2072
+ // Add group column headers
2073
+ report.groups_row.forEach(group => {
2074
+ row.getCell(currentCol).value = group.columnName;
2075
+ currentCol++;
2076
+ });
2077
+
2078
+ // Add field headers
2079
+ currentCol += report.fields_selected.filter(a => a.show).length;
2080
+
2081
+ // Add the date interval headers
2082
+ report.fields_selected.filter(a => a.show).forEach(() => {
2083
+ datedUniqueDates.forEach(date => {
2084
+ row.getCell(currentCol).value = date.dateString;
2085
+ currentCol++;
2086
+ });
2087
+ });
2088
+
2089
+ // Add totals headers
2090
+ report.fields_total.forEach(total => {
2091
+ row.getCell(currentCol).value = 'Totals - ' + toTitleCase(total.type);
2092
+ currentCol++;
2093
+ });
2094
+
2095
+ currentCol = 1;
2096
+ currentRow += 1;
2097
+ row = ws.getRow(currentRow);
2098
+
2099
+ // Populate data for each group and field with date intervals
2100
+ for (let i = 0; i < datedUniqueGroups.length; i++) {
2101
+ let group = datedUniqueGroups[i];
2102
+
2103
+ for (let j = 0; j < report.fields_selected.filter(a => a.show).length; j++) {
2104
+ let field = report.fields_selected.filter(a => a.show)[j];
2105
+
2106
+ currentCol = 1;
2107
+ row = ws.getRow(currentRow);
2108
+
2109
+ // Populate group data
2110
+ report.groups_row.forEach(groupRow => {
2111
+ row.getCell(currentCol).value = (j === 0) ? group[groupRow.id] : '';
2112
+ currentCol++;
2113
+ });
2114
+
2115
+ // Populate field data
2116
+ row.getCell(currentCol).value = field.columnName;
2117
+ currentCol++;
2118
+
2119
+ // Populate date interval data for each field
2120
+ for (let k = 0; k < datedUniqueDates.length; k++) {
2121
+ let result = datedData[k];
2122
+ if (result[i]) {
2123
+ row.getCell(currentCol).value = result[i][field.id] ? result[i][field.id] : '';
2124
+ }
2125
+ else {
2126
+ row.getCell(currentCol).value = '';
2127
+ }
2128
+ currentCol++;
2129
+ }
2130
+
2131
+ // Add totals for each field in the report
2132
+ for (let k = 0; k < report.fields_total.length; k++) {
2133
+ row.getCell(currentCol).value = reportTotalGroups[i][field.id] ? reportTotalGroups[i][field.id] : '';
2134
+ currentCol++;
2135
+ }
2136
+
2137
+ currentRow++; // Move to the next row for the next field
2138
+ }
2139
+ }
2140
+
2141
+ // Add report totals at the bottom
2142
+ if (reportTotals) {
2143
+ for (let i = 0; i < report.fields_total.length; i++) {
2144
+ let total = report.fields_total[i];
2145
+ row = ws.getRow(currentRow);
2146
+ currentCol = 1;
2147
+
2148
+ row.getCell(currentCol).value = toTitleCase(total.type) + ' Totals';
2149
+ currentCol += report.groups_row.length; // Skip group columns
2150
+
2151
+ // Skip the date columns for the totals row
2152
+ report.fields_selected.filter(a => a.show).forEach(() => {
2153
+ currentCol += 1;
2154
+ });
2155
+
2156
+ datedUniqueDates.forEach(() => {
2157
+ currentCol++;
2158
+ });
2159
+
2160
+ // Set totals in the correct fields
2161
+ for (let j = 0; j < report.fields_selected.filter(a => a.show).length; j++) {
2162
+ let field = report.fields_selected.filter(a => a.show)[j];
2163
+ row.getCell(currentCol).value = reportTotals[total.id + '_' + field.id] || '';
2164
+ currentCol++;
2165
+ }
2166
+
2167
+ currentRow++; // Move to the next row for the next total
2168
+ }
2169
+ }
2170
+
2171
+ try {
2172
+ // Save the Excel file and send it via email
2173
+ let buffer = await wb.xlsx.writeBuffer();
2174
+ for (let email of data['emails']) {
2175
+ await this.sendEmail(
2176
+ email,
2177
+ this.serverConfig['CLIENT_NAME'] + ' Scheduled Report - ' + report.report_name,
2178
+ '',
2179
+ `
2180
+ <b>${this.serverConfig['CLIENT_NAME']} Automated Report</b><br>
2181
+ <b>Report Name: </b>${report.report_name}<br>
2182
+ <b>Prepared By: </b>${data['user']}<br><br>
2183
+ Attached are the results of this automated report.<br><br>
2184
+ ResolveIO<br><br>`,
2185
+ [
2186
+ {
2187
+ filename: report.report_name + '-' + moment().format('MM-DD-YYYY-hh-mm-A') + '.xlsx',
2188
+ content: buffer
2189
+ }
2190
+ ],
2191
+ this.serverConfig['MAIL_FROM_REPORTS'],
2192
+ ''
2193
+ );
2194
+ }
2195
+ }
2196
+ catch (err) {
2197
+ console.log('Error writing excel export', err);
2198
+ }
2199
+
2200
+ return true;
2201
+ }
2202
+ }
2203
+ }
2204
+ }
2205
+ catch (err) {
2206
+ err.message = `Error in Report Builder Cron Job - Report Builder Build Tree: ${err.message}`;
2207
+ throw err;
2208
+ }
2209
+
2210
+ return true;
2211
+ }
2212
+ }
2213
+ });
2214
+ }
2215
+
2216
+ function modifyDataTypeField(report, result, field, index) {
2217
+ const processFieldValue = (value, field) => {
2218
+ if (value === undefined || value === null) {
2219
+ return '';
2220
+ }
2221
+
2222
+ if (field.fieldType === 'Boolean') {
2223
+ if (!field.leafFormatType || field.leafFormatType === 'Boolean') {
2224
+ return value === true ? 'True' : 'False';
2225
+ }
2226
+ else if (field.leafFormatType === 'String') {
2227
+ return value === true ? 'Yes' : 'No';
2228
+ }
2229
+ else if (field.leafFormatType === 'Boolean_Number') {
2230
+ return value === true ? 1 : 0;
2231
+ }
2232
+ }
2233
+ else if (field.fieldType === 'Number' || field.leafValueType === 'Count') {
2234
+ if (field.leafFormatType === 'Number') {
2235
+ return value.toLocaleString();
2236
+ }
2237
+ else if (field.leafFormatType === 'String') {
2238
+ return value.toString();
2239
+ }
2240
+ else if (field.leafFormatType === 'Currency') {
2241
+ return round(value, 2).toLocaleString('en-US', { style: 'currency', currency: 'USD' });
2242
+ }
2243
+ }
2244
+ else if (field.fieldType === 'Date') {
2245
+ switch (field.leafFormatType) {
2246
+ case 'Date':
2247
+ return moment(value).format('L');
2248
+ case 'Time':
2249
+ return moment(value).format('LT');
2250
+ case 'DateTime':
2251
+ return moment(value).format('MM/DD/YYYY h:mm A');
2252
+ case 'Date_long':
2253
+ return moment(value).format('LL');
2254
+ case 'DateTime_long':
2255
+ return moment(value).format('LLL');
2256
+ case 'Timestamp':
2257
+ return moment(value).toDate().getTime();
2258
+ default:
2259
+ return value;
2260
+ }
2261
+ }
2262
+
2263
+ return value;
2264
+ };
2265
+
2266
+ const processArrayField = (array, field) => {
2267
+ return array.map(value => processFieldValue(value, field));
2268
+ };
2269
+
2270
+ if (index < report.groups_row.length) {
2271
+ // Handle grouped data
2272
+ const groupKey = 'gr_' + (index + 1);
2273
+ if (result[groupKey]) {
2274
+ result[groupKey].forEach(subResult => {
2275
+ modifyDataTypeField(report, subResult, field, index + 1);
2276
+ });
2277
+ }
2278
+ }
2279
+ else {
2280
+ // Process final-level fields
2281
+ if (Array.isArray(result[field.id])) {
2282
+ result[field.id] = processArrayField(result[field.id], field);
2283
+ }
2284
+ else {
2285
+ result[field.id] = processFieldValue(result[field.id], field);
2286
+ }
2287
+ }
2288
+ }
2289
+
2290
+ function tabGroupExcelRecursive(report, ws: Excel.Worksheet, currentRow, level, result, fields) {
2291
+ let row = ws.getRow(currentRow);
2292
+ const fieldCount = fields.length;
2293
+
2294
+ if (level < report.groups_row.length) {
2295
+ row.getCell(2).fill = {
2296
+ fgColor: {argb: report.groups_row[level - 1].fill_color ? report.groups_row[level - 1].fill_color.replace('#', '') : 'ffffff'},
2297
+ type: 'pattern',
2298
+ pattern: 'solid'
2299
+ };
2300
+ row.getCell(2).font = {
2301
+ color: {argb: report.groups_row[level - 1].font_color ? report.groups_row[level - 1].font_color.replace('#', '') : '000000'},
2302
+ name: 'Arial',
2303
+ bold: true,
2304
+ size: 14
2305
+ };
2306
+
2307
+ const currentGroup = report.groups_row[level - 1];
2308
+ row.getCell(2).value = currentGroup.columnName + ': ' + (result._id[currentGroup.id] || '');
2309
+ ws.mergeCells(row.getCell(2).$col$row + ':' + row.getCell(fieldCount + 1).$col$row);
2310
+
2311
+ row.getCell(2).border = {
2312
+ top: {style:'thick'},
2313
+ left: {style:'thick'},
2314
+ right: {style: 'thick'}
2315
+ };
2316
+
2317
+ if (report.fields_total.length) {
2318
+ report.fields_total.forEach(total => {
2319
+ currentRow += 1;
2320
+ row = ws.getRow(currentRow);
2321
+
2322
+ fields.forEach((field, fieldIndex) => {
2323
+ if (total.fields.includes(field.id)) {
2324
+ row.getCell(fieldIndex + 2).value = field.columnName;
2325
+
2326
+ row.getCell(fieldIndex + 2).font = {
2327
+ name: 'Arial',
2328
+ bold: true,
2329
+ size: 10
2330
+ };
2331
+ }
2332
+
2333
+ if (field.leafFormatType === 'Currency') {
2334
+ row.getCell(fieldIndex + 2).numFmt = '"$"#,##0.00';
2335
+ }
2336
+ else if (field.leafFormatType === 'Number') {
2337
+ row.getCell(fieldIndex + 2).numFmt = '#,##0';
2338
+ }
2339
+
2340
+ if (fieldIndex === 0) {
2341
+ row.getCell(fieldIndex + 2).border = {
2342
+ left: {style:'thick'}
2343
+ };
2344
+ }
2345
+ else if (fieldIndex === fieldCount - 1) {
2346
+ row.getCell(fieldIndex + 2).border = {
2347
+ right: {style:'thick'}
2348
+ };
2349
+ }
2350
+ });
2351
+ });
2352
+ }
2353
+
2354
+ // Totals
2355
+ report.fields_total.forEach(total => {
2356
+ currentRow += 1;
2357
+ row = ws.getRow(currentRow);
2358
+
2359
+ fields.forEach((field, fieldIndex) => {
2360
+ if (total.fields.includes(field.id)) {
2361
+ row.getCell(fieldIndex + 2).value = result[total.id + '_' + field.id];
2362
+
2363
+ row.getCell(fieldIndex + 2).font = {
2364
+ name: 'Arial',
2365
+ bold: true,
2366
+ size: 10
2367
+ };
2368
+
2369
+ if (field.leafFormatType === 'Currency') {
2370
+ row.getCell(fieldIndex + 2).numFmt = '"$"#,##0.00';
2371
+ }
2372
+ else if (field.leafFormatType === 'Number') {
2373
+ row.getCell(fieldIndex + 2).numFmt = '#,##0';
2374
+ }
2375
+ }
2376
+
2377
+ if (fieldIndex === 0) {
2378
+ row.getCell(fieldIndex + 2).border = {
2379
+ top: {style:'thin'},
2380
+ left: {style:'thick'},
2381
+ bottom: {style:'thick'}
2382
+ };
2383
+ }
2384
+ else if (fieldIndex === fieldCount - 1) {
2385
+ row.getCell(fieldIndex + 2).border = {
2386
+ top: {style:'thin'},
2387
+ bottom: {style:'thick'},
2388
+ right: {style:'thick'}
2389
+ };
2390
+ }
2391
+ else {
2392
+ row.getCell(fieldIndex + 2).border = {
2393
+ top: {style:'thin'},
2394
+ bottom: {style:'thick'}
2395
+ };
2396
+ }
2397
+ });
2398
+ });
2399
+
2400
+ currentRow += 1;
2401
+
2402
+ const nextGroup = report.groups_row[level];
2403
+ if (nextGroup) {
2404
+ const nextResults = result[nextGroup.id];
2405
+ if (Array.isArray(nextResults)) {
2406
+ nextResults.forEach(tDataRes => {
2407
+ currentRow = tabGroupExcelRecursive(report, ws, currentRow, level + 1, tDataRes, fields);
2408
+ });
2409
+ }
2410
+ }
2411
+ }
2412
+ else {
2413
+ row.getCell(2).fill = {
2414
+ fgColor: {argb: report.groups_row[level - 1].fill_color ? report.groups_row[level - 1].fill_color.replace('#', '') : 'ffffff'},
2415
+ type: 'pattern',
2416
+ pattern: 'solid'
2417
+ };
2418
+ row.getCell(2).font = {
2419
+ color: {argb: report.groups_row[level - 1].font_color ? report.groups_row[level - 1].font_color.replace('#', '') : '000000'},
2420
+ name: 'Arial',
2421
+ bold: true,
2422
+ size: 12
2423
+ };
2424
+
2425
+ const finalGroup = report.groups_row[level - 1];
2426
+ row.getCell(2).value = finalGroup.columnName + ': ' + (result._id[finalGroup.id] || '');
2427
+ ws.mergeCells(row.getCell(2).$col$row + ':' + row.getCell(fieldCount + 1).$col$row);
2428
+
2429
+ row.getCell(2).border = {
2430
+ top: {style:'thick'},
2431
+ left: {style:'thick'},
2432
+ right: {style: 'thick'}
2433
+ };
2434
+
2435
+ currentRow += 1;
2436
+ row = ws.getRow(currentRow);
2437
+ // Headers
2438
+ fields.forEach((field, fieldIndex) => {
2439
+ row.getCell(fieldIndex + 2).value = field.columnName;
2440
+ row.getCell(fieldIndex + 2).font = {
2441
+ name: 'Arial',
2442
+ bold: true,
2443
+ size: 10
2444
+ };
2445
+
2446
+ if (field.leafFormatType === 'Currency') {
2447
+ row.getCell(fieldIndex + 2).numFmt = '"$"#,##0.00';
2448
+ }
2449
+ else if (field.leafFormatType === 'Number') {
2450
+ row.getCell(fieldIndex + 2).numFmt = '#,##0';
2451
+ }
2452
+
2453
+ if (fieldIndex === 0) {
2454
+ row.getCell(fieldIndex + 2).border = {
2455
+ left: {style:'thick'}
2456
+ };
2457
+ }
2458
+ else if (fieldIndex === fieldCount - 1) {
2459
+ row.getCell(fieldIndex + 2).border = {
2460
+ right: {style:'thick'}
2461
+ };
2462
+ }
2463
+ });
2464
+
2465
+ // Data
2466
+ getMaxRows(result).forEach((res, resIndex) => {
2467
+ currentRow += 1;
2468
+ row = ws.getRow(currentRow);
2469
+
2470
+ fields.forEach((field, fieldIndex) => {
2471
+ let value = null;
2472
+
2473
+ if (Array.isArray(result[field.id])) {
2474
+ value = result[field.id][resIndex] !== undefined ? result[field.id][resIndex] : result[field.id][result[field.id].length - 1];
2475
+ }
2476
+ else {
2477
+ value = result[field.id];
2478
+ }
2479
+
2480
+ if (field.fieldType === 'Number' && typeof(value) === 'string' && value) {
2481
+ row.getCell(fieldIndex + 2).value = parseFloat(value.replace(new RegExp(/\,/g), '').replace(new RegExp(/\$/g), ''));
2482
+ }
2483
+ else {
2484
+ row.getCell(fieldIndex + 2).value = value;
2485
+ }
2486
+
2487
+ if (field.leafFormatType === 'Currency') {
2488
+ row.getCell(fieldIndex + 2).numFmt = '"$"#,##0.00';
2489
+ }
2490
+ else if (field.leafFormatType === 'Number') {
2491
+ row.getCell(fieldIndex + 2).numFmt = '#,##0';
2492
+ }
2493
+
2494
+ row.getCell(fieldIndex + 2).font = {
2495
+ name: 'Arial',
2496
+ size: 10
2497
+ };
2498
+
2499
+ if (fieldIndex === 0) {
2500
+ row.getCell(fieldIndex + 2).border = {
2501
+ left: {style:'thick'}
2502
+ };
2503
+ }
2504
+ else if (fieldIndex === fieldCount - 1) {
2505
+ row.getCell(fieldIndex + 2).border = {
2506
+ right: {style:'thick'}
2507
+ };
2508
+ }
2509
+ });
2510
+ });
2511
+
2512
+ // Totals
2513
+ report.fields_total.forEach(total => {
2514
+ currentRow += 1;
2515
+ row = ws.getRow(currentRow);
2516
+
2517
+ fields.forEach((field, fieldIndex) => {
2518
+ if (total.fields.includes(field.id)) {
2519
+ row.getCell(fieldIndex + 2).value = result[total.id + '_' + field.id];
2520
+
2521
+ row.getCell(fieldIndex + 2).font = {
2522
+ name: 'Arial',
2523
+ bold: true,
2524
+ size: 10
2525
+ };
2526
+
2527
+ if (field.leafFormatType === 'Currency') {
2528
+ row.getCell(fieldIndex + 2).numFmt = '"$"#,##0.00';
2529
+ }
2530
+ else if (field.leafFormatType === 'Number') {
2531
+ row.getCell(fieldIndex + 2).numFmt = '#,##0';
2532
+ }
2533
+ }
2534
+
2535
+ if (fieldIndex === 0) {
2536
+ row.getCell(fieldIndex + 2).border = {
2537
+ top: {style:'thin'},
2538
+ left: {style:'thick'},
2539
+ bottom: {style:'thick'}
2540
+ };
2541
+ }
2542
+ else if (fieldIndex === fieldCount - 1) {
2543
+ row.getCell(fieldIndex + 2).border = {
2544
+ top: {style:'thin'},
2545
+ bottom: {style:'thick'},
2546
+ right: {style:'thick'}
2547
+ };
2548
+ }
2549
+ else {
2550
+ row.getCell(fieldIndex + 2).border = {
2551
+ top: {style:'thin'},
2552
+ bottom: {style:'thick'}
2553
+ };
2554
+ }
2555
+ });
2556
+ });
2557
+ }
2558
+
2559
+ return currentRow + 1;
2560
+ }
2561
+
2562
+ function tabGroupExcelWidthRecursive(report, level, result, cols, fields) {
2563
+ if (level < report.groups_row.length) {
2564
+ const nextGroup = report.groups_row[level];
2565
+ if (nextGroup) {
2566
+ const nextResults = result[nextGroup.id];
2567
+ if (Array.isArray(nextResults)) {
2568
+ nextResults.forEach(tDataRes => {
2569
+ cols = tabGroupExcelWidthRecursive(report, level + 1, tDataRes, cols, fields);
2570
+ });
2571
+ }
2572
+ }
2573
+ }
2574
+ else {
2575
+ fields.forEach((field, fieldIndex) => {
2576
+ if (Array.isArray(result[field.id])) {
2577
+ result[field.id].forEach(res => {
2578
+ let length = 10;
2579
+
2580
+ if (res) {
2581
+ length = res.length + 3;
2582
+ }
2583
+
2584
+ if (!cols[fieldIndex]) {
2585
+ cols[fieldIndex] = Math.max(10, field.columnName.length);
2586
+ }
2587
+
2588
+ if (length > cols[fieldIndex]) {
2589
+ cols[fieldIndex] = length;
2590
+ }
2591
+ });
2592
+ }
2593
+ else {
2594
+ let res = result[field.id];
2595
+ let length = 10;
2596
+
2597
+ if (res !== null && res !== undefined) {
2598
+ const resStr = (typeof res === 'string') ? res : `${res}`;
2599
+ length = resStr.length + 3;
2600
+ }
2601
+
2602
+ if (!cols[fieldIndex]) {
2603
+ cols[fieldIndex] = Math.max(10, field.columnName.length);
2604
+ }
2605
+
2606
+ if (length > cols[fieldIndex]) {
2607
+ cols[fieldIndex] = length;
2608
+ }
2609
+ }
2610
+ });
2611
+ }
2612
+
2613
+ return cols;
2614
+ }