rez_core 5.0.169 → 5.0.170

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 (441) hide show
  1. package/.idea/250218_nodejs_core.iml +11 -8
  2. package/.idea/codeStyles/Project.xml +58 -58
  3. package/.idea/codeStyles/codeStyleConfig.xml +4 -4
  4. package/.idea/inspectionProfiles/Project_Default.xml +1 -1
  5. package/.idea/modules.xml +7 -7
  6. package/.idea/prettier.xml +5 -5
  7. package/.idea/vcs.xml +5 -5
  8. package/.prettierrc +3 -3
  9. package/README.md +99 -99
  10. package/dist/module/auth/guards/role.guard.js +3 -3
  11. package/dist/module/filter/service/filter.service.js +19 -19
  12. package/dist/module/integration/examples/usage.example.js +9 -9
  13. package/dist/module/layout_preference/controller/layout_preference.controller.js +3 -0
  14. package/dist/module/layout_preference/controller/layout_preference.controller.js.map +1 -1
  15. package/dist/module/layout_preference/service/layout_preference.service.js +1 -1
  16. package/dist/module/layout_preference/service/layout_preference.service.js.map +1 -1
  17. package/dist/module/meta/controller/entity.controller.d.ts +0 -2
  18. package/dist/module/meta/controller/entity.controller.js +0 -23
  19. package/dist/module/meta/controller/entity.controller.js.map +1 -1
  20. package/dist/module/meta/controller/meta.controller.js +5 -0
  21. package/dist/module/meta/controller/meta.controller.js.map +1 -1
  22. package/dist/module/meta/controller/view-master.controller.js +2 -0
  23. package/dist/module/meta/controller/view-master.controller.js.map +1 -1
  24. package/dist/module/meta/entity.module.js +0 -3
  25. package/dist/module/meta/entity.module.js.map +1 -1
  26. package/dist/module/meta/repository/attribute-master.repository.js +8 -8
  27. package/dist/module/meta/repository/entity-table-column.repository.d.ts +1 -1
  28. package/dist/module/meta/repository/entity-table-column.repository.js +3 -3
  29. package/dist/module/meta/repository/entity-table-column.repository.js.map +1 -1
  30. package/dist/module/meta/repository/entity-table.repository.d.ts +2 -2
  31. package/dist/module/meta/repository/entity-table.repository.js +4 -4
  32. package/dist/module/meta/repository/entity-table.repository.js.map +1 -1
  33. package/dist/module/meta/repository/view-master.repository.d.ts +1 -1
  34. package/dist/module/meta/repository/view-master.repository.js +2 -2
  35. package/dist/module/meta/repository/view-master.repository.js.map +1 -1
  36. package/dist/module/meta/service/entity-dynamic.service.js +17 -17
  37. package/dist/module/meta/service/entity-service-impl.service.d.ts +0 -4
  38. package/dist/module/meta/service/entity-service-impl.service.js +10 -15
  39. package/dist/module/meta/service/entity-service-impl.service.js.map +1 -1
  40. package/dist/module/meta/service/entity-table-column.service.d.ts +1 -2
  41. package/dist/module/meta/service/entity-table-column.service.js +2 -10
  42. package/dist/module/meta/service/entity-table-column.service.js.map +1 -1
  43. package/dist/module/meta/service/entity-table.service.d.ts +1 -1
  44. package/dist/module/meta/service/entity-table.service.js +5 -5
  45. package/dist/module/meta/service/entity-table.service.js.map +1 -1
  46. package/dist/module/meta/service/entity.service.d.ts +0 -2
  47. package/dist/module/meta/service/media-data.service.js +6 -6
  48. package/dist/module/meta/service/resolver.service.js +15 -15
  49. package/dist/module/meta/service/view-master.service.js +1 -1
  50. package/dist/module/meta/service/view-master.service.js.map +1 -1
  51. package/dist/module/module/repository/menu.repository.js +4 -4
  52. package/dist/module/notification/service/notification.service.js +6 -6
  53. package/dist/module/user/controller/login.controller.js +18 -18
  54. package/dist/module/workflow/repository/action.repository.js +2 -2
  55. package/dist/module/workflow/repository/stage.repository.js +8 -8
  56. package/dist/module/workflow/service/action-template-mapping.service.js +2 -2
  57. package/dist/module/workflow/service/action.service.js +5 -5
  58. package/dist/module/workflow/service/entity-modification.service.js +2 -2
  59. package/dist/module/workflow/service/task.service.js +8 -8
  60. package/dist/module/workflow-automation/service/schedule-handler.service.js +9 -9
  61. package/dist/module/workflow-automation/service/workflow-automation.service.js +4 -2
  62. package/dist/module/workflow-automation/service/workflow-automation.service.js.map +1 -1
  63. package/dist/tsconfig.build.tsbuildinfo +1 -1
  64. package/dist/utils/service/reflection-helper.service.js +2 -2
  65. package/docs/modules/event-driven-integration-design.md +91 -91
  66. package/docs/modules/integration.md +250 -250
  67. package/eslint.config.mjs +34 -34
  68. package/nest-cli.json +14 -14
  69. package/package.json +125 -125
  70. package/src/app.controller.ts +12 -12
  71. package/src/app.module.ts +68 -68
  72. package/src/app.service.ts +8 -8
  73. package/src/config/bull.config.ts +69 -69
  74. package/src/config/config.module.ts +17 -17
  75. package/src/config/database.config.ts +48 -48
  76. package/src/constant/global.constant.ts +67 -67
  77. package/src/core.module.ts +94 -94
  78. package/src/decorators/roles.decorator.ts +7 -7
  79. package/src/dtos/response.dto.ts +6 -6
  80. package/src/dtos/response.ts +5 -5
  81. package/src/index.ts +1 -1
  82. package/src/module/auth/auth.module.ts +49 -49
  83. package/src/module/auth/controller/auth.controller.ts +28 -28
  84. package/src/module/auth/guards/google-auth.guard.ts +9 -9
  85. package/src/module/auth/guards/jwt.guard.ts +22 -22
  86. package/src/module/auth/guards/role.guard.ts +68 -68
  87. package/src/module/auth/services/auth.service.ts +56 -56
  88. package/src/module/auth/services/jwt.service.ts +11 -11
  89. package/src/module/auth/strategies/google.strategy.ts +54 -54
  90. package/src/module/auth/strategies/jwt.strategy.ts +58 -58
  91. package/src/module/auth/strategies/local.strategy.ts +13 -13
  92. package/src/module/dashboard/controller/dashboard.controller.ts +36 -36
  93. package/src/module/dashboard/dashboard.module.ts +21 -21
  94. package/src/module/dashboard/entity/dashboard_page_data.entity.ts +27 -27
  95. package/src/module/dashboard/entity/widget_master.entity.ts +18 -18
  96. package/src/module/dashboard/repository/dashboard.repository.ts +51 -51
  97. package/src/module/dashboard/service/dashboard.service.ts +73 -73
  98. package/src/module/enterprise/controller/organization.controller.ts +36 -36
  99. package/src/module/enterprise/enterprise.module.ts +30 -30
  100. package/src/module/enterprise/entity/enterprise.entity.ts +37 -37
  101. package/src/module/enterprise/entity/organization-app-mapping.entity.ts +13 -13
  102. package/src/module/enterprise/entity/organization.entity.ts +92 -92
  103. package/src/module/enterprise/repository/enterprise.repository.ts +31 -31
  104. package/src/module/enterprise/repository/organization.repository.ts +26 -26
  105. package/src/module/enterprise/repository/school.repository.ts +272 -272
  106. package/src/module/enterprise/service/brand.service.ts +5 -5
  107. package/src/module/enterprise/service/enterprise.service.ts +16 -16
  108. package/src/module/enterprise/service/organization-app-mapping.service.ts +4 -4
  109. package/src/module/enterprise/service/organization.service.ts +145 -145
  110. package/src/module/entity_json/controller/entity_json.controller.ts +47 -47
  111. package/src/module/entity_json/entity/entityJson.entity.ts +39 -39
  112. package/src/module/entity_json/entity_json.module.ts +18 -18
  113. package/src/module/entity_json/service/entityJson.repository.ts +37 -37
  114. package/src/module/entity_json/service/entity_json.service.ts +242 -242
  115. package/src/module/export/controller/export.controller.ts +83 -83
  116. package/src/module/export/export.module.ts +14 -14
  117. package/src/module/export/service/export.service.ts +105 -105
  118. package/src/module/filter/controller/filter.controller.ts +84 -84
  119. package/src/module/filter/dto/filter-request.dto.ts +39 -39
  120. package/src/module/filter/entity/saved-filter-detail.entity.ts +41 -41
  121. package/src/module/filter/entity/saved-filter-master.entity.ts +32 -32
  122. package/src/module/filter/filter.module.ts +33 -33
  123. package/src/module/filter/repository/saved-filter.repository.ts +256 -256
  124. package/src/module/filter/repository/saved.filter-detail.repository.ts +19 -19
  125. package/src/module/filter/service/filter-evaluator.service.ts +82 -82
  126. package/src/module/filter/service/filter.service.ts +1319 -1319
  127. package/src/module/filter/service/saved-filter.service.ts +164 -164
  128. package/src/module/ics/controller/ics.controller.ts +21 -21
  129. package/src/module/ics/dto/ics.dto.ts +55 -55
  130. package/src/module/ics/ics.module.ts +13 -13
  131. package/src/module/ics/service/ics.service.ts +57 -57
  132. package/src/module/integration/controller/calender-event.controller.ts +31 -31
  133. package/src/module/integration/controller/integration.controller.ts +662 -662
  134. package/src/module/integration/controller/wrapper.controller.ts +37 -37
  135. package/src/module/integration/dto/create-config.dto.ts +526 -526
  136. package/src/module/integration/entity/integration-config.entity.ts +112 -112
  137. package/src/module/integration/entity/integration-entity-mapper.entity.ts +14 -14
  138. package/src/module/integration/entity/integration-source.entity.ts +17 -17
  139. package/src/module/integration/entity/user-integration.entity.ts +71 -71
  140. package/src/module/integration/examples/usage.example.ts +338 -338
  141. package/src/module/integration/factories/base.factory.ts +7 -7
  142. package/src/module/integration/factories/email.factory.ts +49 -49
  143. package/src/module/integration/factories/integration.factory.ts +121 -121
  144. package/src/module/integration/factories/sms.factory.ts +51 -51
  145. package/src/module/integration/factories/telephone.factory.ts +41 -41
  146. package/src/module/integration/factories/whatsapp.factory.ts +56 -56
  147. package/src/module/integration/integration.module.ts +110 -110
  148. package/src/module/integration/service/calendar-event.service.ts +118 -118
  149. package/src/module/integration/service/integration-entity-mapper.service.ts +17 -17
  150. package/src/module/integration/service/integration-queue.service.ts +229 -229
  151. package/src/module/integration/service/integration.service.ts +2634 -2634
  152. package/src/module/integration/service/oauth.service.ts +224 -224
  153. package/src/module/integration/service/wrapper.service.ts +753 -753
  154. package/src/module/integration/strategies/email/gmail-api.strategy.ts +280 -280
  155. package/src/module/integration/strategies/email/outlook-api.strategy.ts +44 -44
  156. package/src/module/integration/strategies/email/outlook.strategy.ts +64 -64
  157. package/src/module/integration/strategies/email/sendgrid-api.strategy.ts +260 -260
  158. package/src/module/integration/strategies/integration.strategy.ts +97 -97
  159. package/src/module/integration/strategies/sms/gupshup-sms.strategy.ts +146 -146
  160. package/src/module/integration/strategies/sms/msg91-sms.strategy.ts +164 -164
  161. package/src/module/integration/strategies/sms/tubelight-sms.strategy.ts +163 -163
  162. package/src/module/integration/strategies/telephone/ozonetel-voice.strategy.ts +238 -238
  163. package/src/module/integration/strategies/telephone/tubelight-voice.strategy.ts +210 -210
  164. package/src/module/integration/strategies/whatsapp/gupshup-whatsapp.strategy.ts +359 -359
  165. package/src/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.ts +372 -372
  166. package/src/module/integration/strategies/whatsapp/whatsapp-cloud.strategy.ts +403 -403
  167. package/src/module/integration/strategies/whatsapp/whatsapp.strategy.ts +57 -57
  168. package/src/module/layout/controller/layout.controller.ts +47 -47
  169. package/src/module/layout/entity/header-items.entity.ts +28 -28
  170. package/src/module/layout/entity/header-section.entity.ts +19 -19
  171. package/src/module/layout/layout.module.ts +21 -21
  172. package/src/module/layout/repository/header-items.repository.ts +18 -18
  173. package/src/module/layout/repository/header-section.repository.ts +22 -22
  174. package/src/module/layout/service/header-section.service.ts +25 -25
  175. package/src/module/layout_preference/controller/layout_preference.controller.ts +76 -73
  176. package/src/module/layout_preference/entity/layout_preference.entity.ts +28 -28
  177. package/src/module/layout_preference/layout_preference.module.ts +22 -22
  178. package/src/module/layout_preference/repository/layout_preference.repository.ts +65 -65
  179. package/src/module/layout_preference/service/layout_preference.service.ts +191 -191
  180. package/src/module/lead/controller/lead.controller.ts +30 -30
  181. package/src/module/lead/lead.module.ts +14 -14
  182. package/src/module/lead/repository/lead.repository.ts +41 -41
  183. package/src/module/lead/service/lead.service.ts +54 -54
  184. package/src/module/linked_attributes/controller/linked_attributes.controller.ts +37 -37
  185. package/src/module/linked_attributes/entity/linked_attribute.entity.ts +51 -51
  186. package/src/module/linked_attributes/linked_attributes.module.ts +16 -16
  187. package/src/module/linked_attributes/repository/linked_attribute.repository.ts +12 -12
  188. package/src/module/linked_attributes/service/linked_attributes.service.ts +75 -75
  189. package/src/module/listmaster/controller/list-master.controller.ts +230 -230
  190. package/src/module/listmaster/entity/list-master-items.entity.ts +43 -43
  191. package/src/module/listmaster/entity/list-master.entity.ts +33 -33
  192. package/src/module/listmaster/listmaster.module.ts +46 -46
  193. package/src/module/listmaster/repository/list-master-items.repository.ts +173 -173
  194. package/src/module/listmaster/repository/list-master.repository.ts +56 -56
  195. package/src/module/listmaster/service/list-master-engine.ts +19 -19
  196. package/src/module/listmaster/service/list-master-extension.interface.ts +4 -4
  197. package/src/module/listmaster/service/list-master-item.service.ts +281 -281
  198. package/src/module/listmaster/service/list-master-registry.ts +15 -15
  199. package/src/module/listmaster/service/list-master.service.ts +535 -535
  200. package/src/module/mapper/controller/field-mapper.controller.ts +76 -76
  201. package/src/module/mapper/controller/mapper.controller.ts +20 -20
  202. package/src/module/mapper/dto/field-mapper.dto.ts +14 -14
  203. package/src/module/mapper/entity/field-lovs.entity.ts +19 -19
  204. package/src/module/mapper/entity/field-mapper.entity.ts +53 -53
  205. package/src/module/mapper/entity/mapper.entity.ts +16 -16
  206. package/src/module/mapper/mapper.module.ts +35 -35
  207. package/src/module/mapper/repository/field-lovs.repository.ts +35 -35
  208. package/src/module/mapper/repository/field-mapper.repository.ts +42 -42
  209. package/src/module/mapper/repository/mapper.repository.ts +32 -32
  210. package/src/module/mapper/service/field-mapper.service.ts +269 -269
  211. package/src/module/mapper/service/mapper.service.ts +81 -81
  212. package/src/module/master/controller/master.controller.ts +74 -74
  213. package/src/module/master/service/master.service.ts +483 -483
  214. package/src/module/meta/controller/app-master.controller.ts +38 -38
  215. package/src/module/meta/controller/attribute-master.controller.ts +84 -84
  216. package/src/module/meta/controller/entity-dynamic.controller.ts +125 -125
  217. package/src/module/meta/controller/entity-master.controller.ts +41 -41
  218. package/src/module/meta/controller/entity-relation.controller.ts +36 -36
  219. package/src/module/meta/controller/entity.controller.ts +308 -342
  220. package/src/module/meta/controller/entity.public.controller.ts +75 -75
  221. package/src/module/meta/controller/media.controller.ts +135 -135
  222. package/src/module/meta/controller/meta.controller.ts +101 -96
  223. package/src/module/meta/controller/view-master.controller.ts +79 -86
  224. package/src/module/meta/dto/entity-list-data.dto.ts +6 -6
  225. package/src/module/meta/dto/entity-tab.dto.ts +4 -4
  226. package/src/module/meta/dto/entity-table.dto.ts +12 -12
  227. package/src/module/meta/entity/app-master.entity.ts +37 -37
  228. package/src/module/meta/entity/attribute-master.entity.ts +92 -92
  229. package/src/module/meta/entity/base-entity.entity.ts +75 -75
  230. package/src/module/meta/entity/entity-master.entity.ts +85 -85
  231. package/src/module/meta/entity/entity-relation-data.entity.ts +29 -29
  232. package/src/module/meta/entity/entity-relation.entity.ts +23 -23
  233. package/src/module/meta/entity/entity-table-column.entity.ts +61 -61
  234. package/src/module/meta/entity/entity-table.entity.ts +50 -50
  235. package/src/module/meta/entity/media-data.entity.ts +32 -32
  236. package/src/module/meta/entity/preference.entity.ts +62 -62
  237. package/src/module/meta/entity/view-master.entity.ts +41 -41
  238. package/src/module/meta/entity.module.ts +165 -168
  239. package/src/module/meta/repository/app-master.repository.ts +20 -20
  240. package/src/module/meta/repository/attribute-master.repository.ts +164 -164
  241. package/src/module/meta/repository/entity-attribute-update.repository.ts +48 -48
  242. package/src/module/meta/repository/entity-master.repository.ts +106 -106
  243. package/src/module/meta/repository/entity-relation.repository.ts +22 -22
  244. package/src/module/meta/repository/entity-table-column.repository.ts +39 -39
  245. package/src/module/meta/repository/entity-table.repository.ts +53 -53
  246. package/src/module/meta/repository/media-data.repository.ts +50 -50
  247. package/src/module/meta/repository/preference.repository.ts +20 -20
  248. package/src/module/meta/repository/user-app-mapping.repository.ts +28 -28
  249. package/src/module/meta/repository/view-master.repository.ts +42 -42
  250. package/src/module/meta/service/app-master.service.ts +37 -37
  251. package/src/module/meta/service/attribute-master.service.ts +131 -131
  252. package/src/module/meta/service/common.service.ts +9 -9
  253. package/src/module/meta/service/entity-attribute-update.service.ts +26 -26
  254. package/src/module/meta/service/entity-dynamic.service.ts +826 -826
  255. package/src/module/meta/service/entity-master.service.ts +171 -171
  256. package/src/module/meta/service/entity-realation-data.service.ts +9 -9
  257. package/src/module/meta/service/entity-relation.service.ts +74 -74
  258. package/src/module/meta/service/entity-service-impl.service.ts +388 -439
  259. package/src/module/meta/service/entity-table-column.service.ts +26 -39
  260. package/src/module/meta/service/entity-table.service.ts +157 -157
  261. package/src/module/meta/service/entity-validation.service.ts +187 -187
  262. package/src/module/meta/service/entity.service.ts +49 -59
  263. package/src/module/meta/service/field-group.service.ts +103 -103
  264. package/src/module/meta/service/media-data.service.ts +591 -591
  265. package/src/module/meta/service/populate-meta.service.ts +222 -222
  266. package/src/module/meta/service/preference.service.ts +16 -16
  267. package/src/module/meta/service/resolver.service.ts +291 -291
  268. package/src/module/meta/service/section-master.service.ts +104 -104
  269. package/src/module/meta/service/update-form-json.service.ts +22 -22
  270. package/src/module/meta/service/user-app-mapping.service.ts +17 -17
  271. package/src/module/meta/service/view-master.service.ts +127 -127
  272. package/src/module/microservice-client/microservice-clients.module.ts +13 -13
  273. package/src/module/microservice-client/service/microservice-client-factory.ts +37 -37
  274. package/src/module/microservice-client/service/microservice-clients.ts +4 -4
  275. package/src/module/module/controller/menu.controller.ts +15 -15
  276. package/src/module/module/controller/module-access.controller.ts +133 -133
  277. package/src/module/module/entity/menu.entity.ts +43 -43
  278. package/src/module/module/entity/module-access.entity.ts +25 -25
  279. package/src/module/module/entity/module-action.entity.ts +17 -17
  280. package/src/module/module/entity/module.entity.ts +52 -52
  281. package/src/module/module/module.module.ts +42 -42
  282. package/src/module/module/repository/menu.repository.ts +186 -186
  283. package/src/module/module/repository/module-access.repository.ts +344 -344
  284. package/src/module/module/service/menu.service.ts +82 -82
  285. package/src/module/module/service/module-access.service.ts +189 -189
  286. package/src/module/notification/controller/notification.controller.ts +58 -58
  287. package/src/module/notification/controller/otp.controller.ts +117 -117
  288. package/src/module/notification/entity/notification.entity.ts +26 -26
  289. package/src/module/notification/entity/otp.entity.ts +28 -28
  290. package/src/module/notification/firebase-admin.config.ts +22 -22
  291. package/src/module/notification/notification.module.ts +69 -69
  292. package/src/module/notification/repository/otp.repository.ts +27 -27
  293. package/src/module/notification/service/email.service.ts +127 -127
  294. package/src/module/notification/service/notification.service.ts +164 -164
  295. package/src/module/notification/service/otp.service.ts +133 -133
  296. package/src/module/third-party-module/entity/third-party-api-registry.entity.ts +52 -52
  297. package/src/module/third-party-module/repository/third-party-api-registry.repository.ts +20 -20
  298. package/src/module/third-party-module/service/api-registry.service.ts +13 -13
  299. package/src/module/third-party-module/third-party.module.ts +12 -12
  300. package/src/module/user/controller/login.controller.ts +198 -198
  301. package/src/module/user/controller/user.controller.ts +40 -40
  302. package/src/module/user/dto/create-user.dto.ts +62 -62
  303. package/src/module/user/dto/update-user.dto.ts +4 -4
  304. package/src/module/user/entity/role.entity.ts +33 -33
  305. package/src/module/user/entity/user-role-mapping.entity.ts +38 -38
  306. package/src/module/user/entity/user-session.entity.ts +73 -73
  307. package/src/module/user/entity/user.entity.ts +59 -59
  308. package/src/module/user/repository/role.repository.ts +96 -96
  309. package/src/module/user/repository/user-role-mapping.repository.ts +126 -126
  310. package/src/module/user/repository/user.repository.ts +50 -50
  311. package/src/module/user/repository/userSession.repository.ts +33 -33
  312. package/src/module/user/service/login.service.ts +326 -326
  313. package/src/module/user/service/role.service.ts +197 -197
  314. package/src/module/user/service/user-role-mapping.service.ts +98 -98
  315. package/src/module/user/service/user-session.service.ts +200 -200
  316. package/src/module/user/service/user.service.ts +368 -368
  317. package/src/module/user/user.module.ts +65 -65
  318. package/src/module/workflow/controller/action-category.controller.ts +54 -54
  319. package/src/module/workflow/controller/action-resource-mapping.controller.ts +23 -23
  320. package/src/module/workflow/controller/action-template-mapping.controller.ts +35 -35
  321. package/src/module/workflow/controller/action.controller.ts +111 -111
  322. package/src/module/workflow/controller/activity-log.controller.ts +55 -55
  323. package/src/module/workflow/controller/comm-template.controller.ts +43 -43
  324. package/src/module/workflow/controller/entity-modification.controller.ts +35 -35
  325. package/src/module/workflow/controller/form-master.controller.ts +43 -43
  326. package/src/module/workflow/controller/stage-group.controller.ts +48 -48
  327. package/src/module/workflow/controller/stage.controller.ts +50 -50
  328. package/src/module/workflow/controller/task.controller.ts +77 -77
  329. package/src/module/workflow/controller/workflow-list-master.controller.ts +44 -44
  330. package/src/module/workflow/controller/workflow-meta.controller.ts +80 -80
  331. package/src/module/workflow/controller/workflow.controller.ts +67 -67
  332. package/src/module/workflow/entity/action-category.entity.ts +38 -38
  333. package/src/module/workflow/entity/action-data.entity.ts +55 -55
  334. package/src/module/workflow/entity/action-resources-mapping.entity.ts +29 -29
  335. package/src/module/workflow/entity/action-template-mapping.entity.ts +17 -17
  336. package/src/module/workflow/entity/action.entity.ts +53 -53
  337. package/src/module/workflow/entity/activity-log.entity.ts +43 -43
  338. package/src/module/workflow/entity/comm-template.entity.ts +43 -43
  339. package/src/module/workflow/entity/entity-modification.entity.ts +38 -38
  340. package/src/module/workflow/entity/form.entity.ts +25 -25
  341. package/src/module/workflow/entity/stage-action-mapping.entity.ts +17 -17
  342. package/src/module/workflow/entity/stage-group.entity.ts +23 -23
  343. package/src/module/workflow/entity/stage-movement-data.entity.ts +38 -38
  344. package/src/module/workflow/entity/stage.entity.ts +20 -20
  345. package/src/module/workflow/entity/task-data.entity.ts +88 -88
  346. package/src/module/workflow/entity/template-attach-mapper.entity.ts +30 -30
  347. package/src/module/workflow/entity/workflow-data.entity.ts +11 -11
  348. package/src/module/workflow/entity/workflow-level-mapping.entity.ts +18 -18
  349. package/src/module/workflow/entity/workflow.entity.ts +20 -20
  350. package/src/module/workflow/repository/action-category.repository.ts +79 -79
  351. package/src/module/workflow/repository/action-data.repository.ts +346 -346
  352. package/src/module/workflow/repository/action.repository.ts +339 -339
  353. package/src/module/workflow/repository/activity-log.repository.ts +148 -148
  354. package/src/module/workflow/repository/comm-template.repository.ts +157 -157
  355. package/src/module/workflow/repository/form-master.repository.ts +50 -50
  356. package/src/module/workflow/repository/stage-group.repository.ts +186 -186
  357. package/src/module/workflow/repository/stage-movement.repository.ts +257 -257
  358. package/src/module/workflow/repository/stage.repository.ts +160 -160
  359. package/src/module/workflow/repository/task.repository.ts +151 -151
  360. package/src/module/workflow/repository/workflow.repository.ts +42 -42
  361. package/src/module/workflow/service/action-category.service.ts +33 -33
  362. package/src/module/workflow/service/action-data.service.ts +62 -62
  363. package/src/module/workflow/service/action-resources-mapping.service.ts +10 -10
  364. package/src/module/workflow/service/action-template-mapping.service.ts +140 -140
  365. package/src/module/workflow/service/action.service.ts +302 -302
  366. package/src/module/workflow/service/activity-log.service.ts +107 -107
  367. package/src/module/workflow/service/comm-template.service.ts +180 -180
  368. package/src/module/workflow/service/entity-modification.service.ts +61 -61
  369. package/src/module/workflow/service/form-master.service.ts +35 -35
  370. package/src/module/workflow/service/populate-workflow.service.ts +320 -320
  371. package/src/module/workflow/service/stage-action-mapping.service.ts +5 -5
  372. package/src/module/workflow/service/stage-group.service.ts +344 -344
  373. package/src/module/workflow/service/stage.service.ts +207 -207
  374. package/src/module/workflow/service/task.service.ts +550 -550
  375. package/src/module/workflow/service/workflow-list-master.service.ts +68 -68
  376. package/src/module/workflow/service/workflow-meta.service.ts +639 -639
  377. package/src/module/workflow/service/workflow.service.ts +213 -213
  378. package/src/module/workflow/workflow.module.ts +180 -180
  379. package/src/module/workflow-automation/SCHEDULING_GUIDE.md +145 -145
  380. package/src/module/workflow-automation/controller/workflow-automation.controller.ts +43 -43
  381. package/src/module/workflow-automation/entity/workflow-automation-action.entity.ts +26 -26
  382. package/src/module/workflow-automation/entity/workflow-automation.entity.ts +40 -40
  383. package/src/module/workflow-automation/interface/action.decorator.ts +7 -7
  384. package/src/module/workflow-automation/interface/action.interface.ts +5 -5
  385. package/src/module/workflow-automation/service/action-registery.service.ts +35 -35
  386. package/src/module/workflow-automation/service/schedule-handler.service.ts +168 -168
  387. package/src/module/workflow-automation/service/workflow-automation-engine.service.ts +219 -219
  388. package/src/module/workflow-automation/service/workflow-automation.service.ts +519 -515
  389. package/src/module/workflow-automation/workflow-automation.module.ts +54 -54
  390. package/src/module/workflow-schedule/INSTALLATION.md +244 -244
  391. package/src/module/workflow-schedule/MULTI_PROJECT_GUIDE.md +196 -196
  392. package/src/module/workflow-schedule/README.md +422 -422
  393. package/src/module/workflow-schedule/constants/schedule.constants.ts +48 -48
  394. package/src/module/workflow-schedule/controller/workflow-schedule.controller.ts +255 -255
  395. package/src/module/workflow-schedule/docs/CLAUDE_CODE_GUIDE.md +510 -510
  396. package/src/module/workflow-schedule/docs/CLAUDE_CODE_PROMPT.md +362 -362
  397. package/src/module/workflow-schedule/docs/RUN_CLAUDE_CODE.sh +68 -68
  398. package/src/module/workflow-schedule/dto/create-schedule.dto.ts +147 -147
  399. package/src/module/workflow-schedule/dto/get-execution-logs.dto.ts +119 -119
  400. package/src/module/workflow-schedule/dto/update-schedule.dto.ts +96 -96
  401. package/src/module/workflow-schedule/entities/scheduled-workflow.entity.ts +148 -148
  402. package/src/module/workflow-schedule/entities/workflow-execution-log.entity.ts +154 -154
  403. package/src/module/workflow-schedule/interfaces/schedule-job-data.interface.ts +53 -53
  404. package/src/module/workflow-schedule/interfaces/workflow-schedule-options.interface.ts +12 -12
  405. package/src/module/workflow-schedule/processors/schedule.processor.ts +620 -620
  406. package/src/module/workflow-schedule/service/workflow-schedule.service.ts +598 -598
  407. package/src/module/workflow-schedule/workflow-schedule.module.ts +67 -67
  408. package/src/resources/dev.properties.yaml +31 -31
  409. package/src/resources/local.properties.yaml +27 -27
  410. package/src/resources/properties.module.ts +12 -12
  411. package/src/resources/properties.yaml.ts +11 -11
  412. package/src/resources/uat.properties.yaml +31 -31
  413. package/src/table.config.ts +133 -133
  414. package/src/utils/dto/excel-data.dto.ts +14 -14
  415. package/src/utils/dto/excelsheet-data.dto.ts +5 -5
  416. package/src/utils/service/base64util.service.ts +18 -18
  417. package/src/utils/service/clockIDGenUtil.service.ts +21 -21
  418. package/src/utils/service/codeGenerator.service.ts +22 -22
  419. package/src/utils/service/dateUtil.service.ts +17 -17
  420. package/src/utils/service/encryptUtil.service.ts +97 -97
  421. package/src/utils/service/excel-helper.service.ts +72 -72
  422. package/src/utils/service/excelUtil.service.ts +15 -15
  423. package/src/utils/service/file-util.service.ts +11 -11
  424. package/src/utils/service/json-util.service.ts +23 -23
  425. package/src/utils/service/loggingUtil.service.ts +88 -88
  426. package/src/utils/service/reflection-helper.service.ts +62 -62
  427. package/src/utils/service/wbsCodeGen.service.ts +8 -8
  428. package/src/utils/utils.module.ts +27 -27
  429. package/tsconfig.build.json +4 -4
  430. package/tsconfig.json +24 -24
  431. package/.claude/settings.local.json +0 -26
  432. package/.idea/copilot.data.migration.agent.xml +0 -6
  433. package/.idea/copilot.data.migration.ask.xml +0 -6
  434. package/.idea/copilot.data.migration.ask2agent.xml +0 -6
  435. package/.idea/copilot.data.migration.edit.xml +0 -6
  436. package/.idea/misc.xml +0 -6
  437. package/dist/module/meta/service/entity-list.service.d.ts +0 -17
  438. package/dist/module/meta/service/entity-list.service.js +0 -142
  439. package/dist/module/meta/service/entity-list.service.js.map +0 -1
  440. package/server.log +0 -850
  441. package/src/module/meta/service/entity-list.service.ts +0 -201
@@ -1,1319 +1,1319 @@
1
- import { BadRequestException, Inject, Injectable } from '@nestjs/common';
2
- import { AttributeMasterService } from 'src/module/meta/service/attribute-master.service';
3
- import { EntityMasterService } from 'src/module/meta/service/entity-master.service';
4
- import { FilterCondition, FilterRequestDto } from '../dto/filter-request.dto';
5
- import { SavedFilterService } from './saved-filter.service';
6
-
7
- import * as moment from 'moment';
8
- import { EntityRelationService } from 'src/module/meta/service/entity-relation.service';
9
- import { ResolverService } from 'src/module/meta/service/resolver.service';
10
- import { LoggingService } from 'src/utils/service/loggingUtil.service';
11
- import { ConfigService } from '@nestjs/config';
12
- import { ReflectionHelper } from '../../../utils/service/reflection-helper.service';
13
- import { EntityManager } from 'typeorm';
14
-
15
- @Injectable()
16
- export class FilterService {
17
- constructor(
18
- private entityManager: EntityManager,
19
- private readonly attributeMasterService: AttributeMasterService,
20
- private readonly entityMasterService: EntityMasterService,
21
- @Inject('SavedFilterService')
22
- private readonly savedFilterService: SavedFilterService,
23
- @Inject('EntityRelationService')
24
- private readonly entityRelationService: EntityRelationService,
25
- private readonly resolverService: ResolverService,
26
- @Inject() protected readonly loggingService: LoggingService,
27
- private readonly configService: ConfigService,
28
- private readonly reflectionHelper: ReflectionHelper,
29
- ) {
30
- }
31
-
32
- schema = this.configService.get('DB_SCHEMA');
33
-
34
- private async gettab_value_counts(
35
- tableName: string,
36
- column: string | undefined,
37
- whereClauses: { query: string; params: Record<string, any> }[],
38
- ) {
39
- if (!column) return [];
40
-
41
- let whereSQL = '';
42
- const values: any[] = [];
43
- let paramIndex = 1; // PostgreSQL placeholders start at $1
44
-
45
- if (whereClauses.length > 0) {
46
- const clauseParts = whereClauses.map((clause) => {
47
- let parsedQuery = clause.query.replace(/\be\./g, ''); // remove e.
48
-
49
- Object.entries(clause.params).forEach(([key, val]) => {
50
- if (Array.isArray(val)) {
51
- // Create ($1,$2,$3)
52
- const placeholders = val.map(() => `$${paramIndex++}`).join(', ');
53
-
54
- parsedQuery = parsedQuery.replace(
55
- new RegExp(`:${key}`, 'g'),
56
- `(${placeholders})`,
57
- );
58
-
59
- values.push(...val.map((v) => String(v)));
60
- } else {
61
- // Create $1
62
- const placeholder = `$${paramIndex++}`;
63
-
64
- parsedQuery = parsedQuery.replace(
65
- new RegExp(`:${key}`, 'g'),
66
- placeholder,
67
- );
68
-
69
- values.push(String(val));
70
- }
71
- });
72
-
73
- return parsedQuery;
74
- });
75
-
76
- whereSQL = `WHERE ${clauseParts.join(' AND ')}`;
77
- }
78
-
79
- const rawSQL = `
80
- SELECT ${column} AS tab_value, COUNT(*) AS tab_value_count
81
- FROM ${this.schema}.${tableName} ${whereSQL}
82
- GROUP BY ${column}
83
- `;
84
-
85
-
86
- const rows = await this.entityManager.query(rawSQL, values);
87
-
88
- const total = rows.reduce(
89
- (sum, r) => sum + parseInt(r.tab_value_count, 10),
90
- 0,
91
- );
92
-
93
- return [
94
- { tab_value: 'All', tab_value_count: total },
95
- ...rows.map((r) => ({
96
- tab_value: r.tab_value ?? 'BLANK',
97
- tab_value_count: parseInt(r.tab_value_count, 10),
98
- })),
99
- ];
100
- }
101
-
102
- async applyFilterWrapper(dto: FilterRequestDto) {
103
- const {
104
- entity_type,
105
- quickFilter = [],
106
- savedFilterCode,
107
- attributeFilter = [],
108
- loggedInUser,
109
- } = dto;
110
-
111
- // 🔹 Step 1: Collect all filters (from body + savedFilter)
112
- const savedFilters = await this.getSavedFilters(
113
- savedFilterCode ?? undefined,
114
- );
115
- const allFilters = [
116
- ...quickFilter,
117
- ...attributeFilter,
118
- ...savedFilters,
119
- ].filter((f) => f.filter_value !== '');
120
-
121
- // 🔹 Step 2: Group filters by filter_entity_type
122
- const grouped = allFilters.reduce(
123
- (acc, f) => {
124
- if (!acc[f.filter_entity_type]) acc[f.filter_entity_type] = [];
125
- acc[f.filter_entity_type].push(f);
126
- return acc;
127
- },
128
- {} as Record<string, any[]>,
129
- );
130
-
131
- console.log('🟠 [FilterService] Grouped filters by entity type:', grouped);
132
-
133
- // 🔹 Step 3: Handle sub-entities first
134
- let intersectionIds: number[] | null = null;
135
- for (const [subEntityType, filters] of Object.entries(grouped)) {
136
- if (subEntityType === entity_type) continue; // skip main entity for now
137
-
138
- let { queryParams, tabs, ...newDto } = dto;
139
-
140
- const subDto: FilterRequestDto = {
141
- ...newDto,
142
- entity_type: subEntityType,
143
- quickFilter: filters as FilterCondition[],
144
- savedFilterCode: null, // already merged
145
- attributeFilter: [],
146
- };
147
-
148
- const subResult = await this.applyFilter(subDto);
149
- const subEntityIds = subResult.data.entity_list.map((row) => row.id);
150
-
151
- console.log(
152
- `🧩 [FilterService] Sub-entity ${subEntityType} returned IDs:`,
153
- subEntityIds,
154
- );
155
-
156
- if (!subEntityIds.length) {
157
- console.log(
158
- `ℹ️ [FilterService] No records for sub-entity ${subEntityType}, returning empty result`,
159
- );
160
- return {
161
- success: true,
162
- data: { entity_tabs: [], entity_list: [], pagination: {} },
163
- };
164
- }
165
-
166
- const relatedIds = await this.entityRelationService.getRelatedEntityIds(
167
- entity_type,
168
- subEntityType,
169
- subEntityIds,
170
- dto.loggedInUser.enterprise_id,
171
- );
172
-
173
- intersectionIds =
174
- intersectionIds === null
175
- ? relatedIds
176
- : intersectionIds.filter((id) => relatedIds.includes(id));
177
-
178
- if (intersectionIds.length === 0) {
179
- console.log(
180
- '🚫 [FilterService] No intersection IDs left, returning empty result',
181
- );
182
- return {
183
- success: true,
184
- data: { entity_tabs: [], entity_list: [], pagination: {} },
185
- };
186
- }
187
- }
188
-
189
- // 🔹 Step 4: Call applyFilter for main entity
190
- const mainFilters = grouped[entity_type] || [];
191
- const mainDto: FilterRequestDto = {
192
- ...dto,
193
- quickFilter: [...mainFilters],
194
- savedFilterCode: null,
195
- attributeFilter: [],
196
- };
197
-
198
- if (intersectionIds && intersectionIds.length > 0) {
199
- (mainDto.quickFilter ??= []).push({
200
- filter_attribute: 'id',
201
- filter_operator: 'equal',
202
- filter_value: intersectionIds,
203
- filter_entity_type: entity_type,
204
- });
205
- }
206
-
207
- return await this.applyFilter(mainDto);
208
- }
209
-
210
- async applyFilter(dto: FilterRequestDto) {
211
- const {
212
- entity_type,
213
- quickFilter,
214
- savedFilterCode,
215
- attributeFilter,
216
- tabs,
217
- sortby,
218
- loggedInUser,
219
- queryParams,
220
- customLevelType,
221
- customLevelId,
222
- customAppCode,
223
- } = dto;
224
-
225
- // abstract user details
226
- const {
227
- level_type,
228
- level_id,
229
- id: user_id,
230
- appcode,
231
- organization_id,
232
- } = loggedInUser || {};
233
-
234
- // Fetch meta from entity table service
235
- const entityMeta = await this.entityMasterService.getEntityData(
236
- entity_type,
237
- loggedInUser,
238
- );
239
- const tableName = entityMeta?.data_source; // data_source is the table name
240
-
241
- if (!tableName) {
242
- console.error(`❌ [FilterService] Invalid entity_type: ${entity_type}`);
243
- throw new BadRequestException(`Invalid entity_type: ${entity_type}`);
244
- }
245
-
246
- const getAttributeColumnMeta =
247
- await this.attributeMasterService.findAttributesByMappedEntityType(
248
- entity_type,
249
- loggedInUser,
250
- );
251
-
252
- const attributeMetaMap = getAttributeColumnMeta.reduce(
253
- (acc, attr) => {
254
- acc[attr.attribute_key] = attr;
255
- return acc;
256
- },
257
- {} as Record<string, any>,
258
- );
259
-
260
- // Get and parse saved filters
261
- const savedFilters = await this.getSavedFilters(
262
- savedFilterCode ?? undefined,
263
- );
264
- const savedFiltersNormalized = savedFilters.map((f) => ({
265
- filter_attribute: f.filter_attribute,
266
- filter_operator: f.filter_operator,
267
- filter_value: f.filter_value,
268
- }));
269
-
270
- const baseFilters = [
271
- ...(quickFilter || []).filter(
272
- (f) => f.filter_value != null && f.filter_value !== '',
273
- ),
274
- ...savedFiltersNormalized.filter(
275
- (f) => f.filter_value != null && f.filter_value !== '',
276
- ),
277
- ...(attributeFilter || []).filter(
278
- (f) => f.filter_value != null && f.filter_value !== '',
279
- ),
280
- ];
281
-
282
- // 🧱 Build where clausesx
283
- const baseWhere = this.buildWhereClauses(baseFilters, attributeMetaMap);
284
-
285
- // Handle TEMPLATE entity special condition
286
- if (entity_type === 'TEMP' || entity_type === 'TEM') {
287
- const templateTable = entityMeta?.data_source || 'frm_wf_comm_template';
288
-
289
- if (level_type === 'ORG') {
290
- baseWhere.push({
291
- query: ` ((e.level_type = 'ORG' AND e.level_id = '${loggedInUser.organization_id}' AND e.organization_id = '${loggedInUser.organization_id}'))`,
292
- params: {},
293
- });
294
- } else if (level_type === 'SCH') {
295
- baseWhere.push({
296
- query: `
297
- (
298
- (e.level_type = 'SCH' AND e.level_id = '${loggedInUser.level_id}' AND e.organization_id = '${loggedInUser.organization_id}')
299
- OR
300
- (
301
- e.level_type = 'ORG'
302
- AND e.level_id = '${loggedInUser.organization_id}'
303
- AND e.organization_id = '${loggedInUser.organization_id}'
304
- AND e.code NOT IN (
305
- SELECT sub.code
306
- FROM frm_wf_comm_template AS sub
307
- WHERE sub.level_type = 'SCH' AND sub.level_id = '${loggedInUser.level_id}'
308
- )
309
- )
310
- )
311
- `,
312
- params: {},
313
- });
314
- }
315
- }
316
-
317
- // Default org/level clause — skip TEMPLATE entity
318
- if (
319
- entity_type !== 'ORGP' &&
320
- entity_type !== 'TEMP' &&
321
- entity_type !== 'TEM' && // ✅ skip TEMPLATE
322
- level_type &&
323
- level_id &&
324
- organization_id &&
325
- !customLevelType &&
326
- !customLevelId
327
- ) {
328
- baseWhere.push({
329
- query:
330
- 'e.organization_id = :organization_id AND e.level_type = :level_type AND e.level_id = :level_id',
331
- params: {
332
- organization_id: String(organization_id),
333
- level_type,
334
- level_id,
335
- },
336
- });
337
- }
338
-
339
- if (entity_type == 'USR' || entity_type == 'UPR') {
340
- baseWhere.push({
341
- query: 'e.is_customer is NULL',
342
- params: {},
343
- });
344
- }
345
-
346
- if (customLevelType && customLevelId && customAppCode) {
347
- baseWhere.push({
348
- query:
349
- 'e.organization_id = :organization_id AND e.level_type = :customLevelType AND e.level_id = :customLevelId AND e.appcode = :customAppCode',
350
- params: {
351
- organization_id: String(organization_id),
352
- customLevelType,
353
- customLevelId: String(customLevelId),
354
- customAppCode,
355
- },
356
- });
357
- }
358
-
359
- // Handle queryParams filters
360
- if (queryParams) {
361
- Object.entries(queryParams).forEach(([key, value]) => {
362
- if (!value) return;
363
-
364
- // Always convert to string
365
- const strValue = String(value);
366
-
367
- if (key === 'attributeName' && queryParams['attributeValue']) {
368
- const attrName = strValue;
369
- const attrValue = String(queryParams['attributeValue']);
370
-
371
- baseWhere.push({
372
- query: `e.${attrName} = :${attrName}`,
373
- params: { [attrName]: attrValue }, // <-- string
374
- });
375
- } else if (key !== 'attributeValue') {
376
- baseWhere.push({
377
- query: `e.${key} = :${key}`,
378
- params: { [key]: strValue }, // <-- string
379
- });
380
- }
381
- });
382
- }
383
-
384
- console.log('🟠 [FilterService] Constructed baseWhere clauses:', baseWhere);
385
-
386
- let layoutPreferenceRepo =
387
- this.reflectionHelper.getRepoService('LayoutPreference');
388
-
389
- const layoutPreference = await layoutPreferenceRepo.findOne({
390
- where: {
391
- user_id: user_id,
392
- mapped_entity_type: entity_type,
393
- mapped_level_id: level_id,
394
- mapped_level_type: level_type,
395
- type: 'layout',
396
- },
397
- });
398
-
399
- // Extract layout preference
400
- const layout = layoutPreference?.mapped_json?.quick_tab || {};
401
- const showList =
402
- layout?.show_list?.map((val) => {
403
- if (typeof val === 'string') {
404
- // legacy case — old format like ['Active', 'Inactive']
405
- return val.toLowerCase();
406
- }
407
-
408
- if (val && typeof val === 'object' && val.value !== undefined) {
409
- // new format — [{ label: 'Active', value: '13023' }]
410
- return String(val.value).toLowerCase();
411
- }
412
-
413
- return ''; // fallback safety
414
- }) || [];
415
-
416
- let filteredTabs = await this.getFilteredTabs(
417
- layout,
418
- showList,
419
- entityMeta,
420
- baseWhere,
421
- tabs,
422
- loggedInUser,
423
- );
424
-
425
- const dataWhere = [...baseWhere];
426
-
427
- if (tabs?.columnName && tabs?.value) {
428
- const tabAttrMeta = attributeMetaMap[tabs.columnName];
429
- const tabValue = tabs.value.toLowerCase();
430
-
431
- if (tabValue === 'others') {
432
- // Extract 'value' (IDs) from showList, ignore 'all'
433
- const valuesToExclude = showList
434
- .filter((v) => v.label.toLowerCase() !== 'all')
435
- .map((v) => v.value);
436
-
437
- if (valuesToExclude.length > 0) {
438
- for (const value of valuesToExclude) {
439
- const resolvedId = await this.resolverService.getResolvedId(
440
- loggedInUser,
441
- tabs.columnName,
442
- value,
443
- entity_type,
444
- );
445
- if (resolvedId) {
446
- const tabCondition = this.buildCondition(
447
- {
448
- filter_attribute: tabs.columnName,
449
- filter_operator: 'not_equal',
450
- filter_value: [resolvedId],
451
- skip_id: true,
452
- },
453
- tabAttrMeta,
454
- );
455
- if (tabCondition) dataWhere.push(tabCondition);
456
- }
457
- }
458
- }
459
- } else if (tabValue !== 'all') {
460
- const resolvedId = await this.resolverService.getResolvedId(
461
- loggedInUser,
462
- tabs.columnName,
463
- tabs.value,
464
- entity_type,
465
- );
466
-
467
- if (resolvedId) {
468
- const tabCondition = this.buildCondition(
469
- {
470
- filter_attribute: tabs.columnName,
471
- filter_operator: 'equal',
472
- filter_value: [resolvedId],
473
- skip_id: true,
474
- },
475
- tabAttrMeta,
476
- );
477
- if (tabCondition) dataWhere.push(tabCondition);
478
- }
479
- }
480
- }
481
-
482
- let qb = this.entityManager
483
- .createQueryBuilder()
484
- .select('e.*')
485
- .from(`${this.schema}.${tableName}`, 'e');
486
- dataWhere.forEach((clause) => qb.andWhere(clause.query, clause.params));
487
-
488
- // Apply sorting
489
- qb = await this.sortTabsByShowList(qb, sortby, layoutPreference, tabs);
490
- const page = dto.page && dto.page > 0 ? dto.page : 1;
491
- const size = dto.size && dto.size > 0 ? dto.size : 10;
492
- if (dto.page && dto.size) {
493
- qb.skip((page - 1) * size).take(size);
494
- }
495
-
496
- let query = await qb.getQuery();
497
-
498
- console.log('------------------------------------------------------\n');
499
- console.log(query);
500
- console.log('\n------------------------------------------------------');
501
- const entity_list = await qb.getRawMany();
502
-
503
- console.log(`📦 [FilterService] Fetched ${entity_list.length} records`);
504
-
505
- const dateAttributes = Object.entries(attributeMetaMap)
506
- .filter(([_, attr]) => attr.data_type === 'date')
507
- .map(([key]) => key);
508
-
509
- const formatDate = (dateStr: string | null): string | null =>
510
- dateStr ? moment(dateStr).format('DD-MM-YYYY') : '';
511
-
512
- const formatDatesInRow = (row: any): any => {
513
- const formattedRow = { ...row };
514
- for (const key of dateAttributes) {
515
- if (formattedRow[key])
516
- formattedRow[key] = formatDate(formattedRow[key]);
517
- }
518
- return formattedRow;
519
- };
520
-
521
- const formattedEntityList = entity_list.map(formatDatesInRow);
522
-
523
- const resolvedEntityList = await Promise.all(
524
- formattedEntityList.map((row) =>
525
- this.resolverService.getResolvedData(loggedInUser, row, entity_type),
526
- ),
527
- );
528
-
529
- const resolvedTabs = await Promise.all(
530
- filteredTabs.map(async (tab) => {
531
- const tabAttrKey = layout?.attribute || tabs?.columnName;
532
-
533
- if (
534
- !tabAttrKey ||
535
- tab.tab_value?.toLowerCase() === 'all' ||
536
- tab.tab_value?.toLowerCase() === 'others'
537
- ) {
538
- return tab;
539
- }
540
-
541
- const resolvedVal = await this.resolverService.getResolvedValue(
542
- loggedInUser,
543
- tabAttrKey,
544
- tab.tab_value,
545
- entity_type,
546
- );
547
-
548
- return { ...tab, tab_value: resolvedVal ?? tab.tab_value };
549
- }),
550
- );
551
-
552
- const countQb = this.entityManager
553
- .createQueryBuilder()
554
- .select('COUNT(*)', 'count')
555
- .from(`${this.schema}.${tableName}`, 'e');
556
- dataWhere.forEach((clause) =>
557
- countQb.andWhere(clause.query, clause.params),
558
- );
559
- const countResult = await countQb.getRawOne();
560
- const total = parseInt(countResult.count, 10);
561
-
562
- console.log('📊 [FilterService] Returning final result with total:', total);
563
-
564
- return {
565
- success: true,
566
- data: {
567
- entity_tabs: resolvedTabs,
568
- entity_list: resolvedEntityList,
569
- pagination: {
570
- total,
571
- page,
572
- size,
573
- totalPages: Math.ceil(total / size),
574
- hasNextPage: page * size < total,
575
- hasPreviousPage: page > 1,
576
- },
577
- },
578
- };
579
- }
580
-
581
- // GET FILTERED TABS LOGIC
582
- private async getFilteredTabs(
583
- layout: any,
584
- showList: any,
585
- entityMeta: any,
586
- baseWhere: any,
587
- tabs: any,
588
- loggedInUser: any,
589
- ) {
590
- let allTabs;
591
- if (layout.attribute) {
592
- allTabs = await this.gettab_value_counts(
593
- entityMeta?.data_source,
594
- layout.attribute,
595
- baseWhere,
596
- );
597
- } else {
598
- allTabs = await this.gettab_value_counts(
599
- entityMeta?.data_source,
600
- tabs?.columnName,
601
- baseWhere,
602
- );
603
- }
604
-
605
- let filteredTabs: any[] = [];
606
-
607
- if (showList?.length > 0) {
608
- // Extract tab IDs (values)
609
- const showValues = (showList || []).map((item) => {
610
- const val = typeof item === 'object' ? item.value : item;
611
- return val ? String(val).toLowerCase() : '';
612
- });
613
-
614
- // Handle "all" logic
615
- const isAllNeeded = layout?.isAllSelected && !showValues.includes('all');
616
- if (isAllNeeded) {
617
- showList.push({ label: 'All', value: 'all' });
618
- showValues.push('all');
619
- }
620
-
621
- // Filter by value IDs (not labels)
622
- filteredTabs = allTabs.filter((tab) => {
623
- const tabValueStr = tab.tab_value
624
- ? String(tab.tab_value).toLowerCase()
625
- : '';
626
- return showValues.includes(tabValueStr);
627
- });
628
-
629
- // Handle "all" tab
630
- const allTab = filteredTabs.find(
631
- (tab) => tab.tab_value?.toString().toLowerCase() === 'all',
632
- );
633
- filteredTabs = filteredTabs.filter(
634
- (tab) => tab.tab_value?.toString().toLowerCase() !== 'all',
635
- );
636
-
637
- // SORTING LOGIC
638
- if (layout.sorting === 'asc') {
639
- filteredTabs.sort((a, b) =>
640
- a.tab_value.toString().localeCompare(b.tab_value.toString()),
641
- );
642
- } else if (layout.sorting === 'dsc') {
643
- filteredTabs.sort((a, b) =>
644
- b.tab_value.toString().localeCompare(a.tab_value.toString()),
645
- );
646
- } else if (layout.sorting === 'count_asc') {
647
- filteredTabs.sort((a, b) => a.tab_value_count - b.tab_value_count);
648
- } else if (layout.sorting === 'count_dsc') {
649
- filteredTabs.sort((a, b) => b.tab_value_count - a.tab_value_count);
650
- } else if (layout.sorting === 'custom') {
651
- // Keep order same as showList
652
- const orderMap = new Map<string, number>(
653
- showList.map((item, index) => [
654
- item.value?.toString().toLowerCase(),
655
- index,
656
- ]),
657
- );
658
- filteredTabs.sort((a, b) => {
659
- const aIndex =
660
- orderMap.get(a.tab_value?.toString().toLowerCase()) ?? Infinity;
661
- const bIndex =
662
- orderMap.get(b.tab_value?.toString().toLowerCase()) ?? Infinity;
663
- return aIndex - bIndex;
664
- });
665
- }
666
-
667
- // Re-add "all" tab at beginning if needed
668
- if (allTab) filteredTabs.unshift(allTab);
669
-
670
- // Combine others if enabled
671
- if (layout?.isCombineOther) {
672
- const originalAllTab = allTabs.find(
673
- (tab) => tab.tab_value?.toString().toLowerCase() === 'all',
674
- );
675
- const allCount = originalAllTab?.tab_value_count ?? 0;
676
- const knownTabCountSum = filteredTabs
677
- .filter((tab) => tab.tab_value?.toString().toLowerCase() !== 'all')
678
- .reduce((acc, tab) => acc + tab.tab_value_count, 0);
679
-
680
- const othersCount = allCount - knownTabCountSum;
681
- filteredTabs.push({
682
- tab_value: 'OTHERS',
683
- tab_value_count: othersCount < 0 ? 0 : othersCount,
684
- });
685
- }
686
- } else {
687
- filteredTabs = allTabs;
688
- }
689
-
690
- return filteredTabs;
691
- }
692
-
693
- // SORTING LOGIC FOR TABS
694
- private async sortTabsByShowList(
695
- qb: any,
696
- sortby: any,
697
- layoutPreference: any,
698
- tabs: any,
699
- ) {
700
- if (layoutPreference && layoutPreference[0]?.mapped_json?.sorting) {
701
- if (Array.isArray(layoutPreference[0]?.mapped_json?.sorting?.tabs)) {
702
- const preferenceTabArray =
703
- layoutPreference[0]?.mapped_json?.sorting?.tabs;
704
- const tabFilter = preferenceTabArray.find(
705
- (tabData) => tabData.tab_name === tabs?.value,
706
- );
707
- tabFilter?.sortby.forEach(({ column, order }) => {
708
- qb.addOrderBy(
709
- `e.${column}`,
710
- order?.toUpperCase() === 'DSC' ? 'DESC' : 'ASC',
711
- );
712
- });
713
- } else if (
714
- Array.isArray(layoutPreference[0]?.mapped_json?.sorting?.sortby)
715
- ) {
716
- layoutPreference[0]?.mapped_json?.sorting?.sortby?.forEach(
717
- ({ column, order }) => {
718
- qb.addOrderBy(
719
- `e.${column}`,
720
- order?.toUpperCase() === 'DSC' ? 'DESC' : 'ASC',
721
- );
722
- },
723
- );
724
- }
725
- }
726
-
727
- if (Array.isArray(sortby) && sortby.length > 0) {
728
- sortby.forEach(({ sortColum, sortType }) => {
729
- if (sortColum) {
730
- qb.addOrderBy(
731
- `e.${sortColum}`,
732
- sortType?.toUpperCase() === 'DSC' ? 'DESC' : 'ASC',
733
- );
734
- }
735
- });
736
- } else {
737
- // fallback if no explicit sortby sent
738
- qb.addOrderBy('e.created_date', 'DESC');
739
- }
740
-
741
- return qb;
742
- }
743
-
744
- private parseFilters(raw: any, isSingle = false): any[] {
745
- if (!raw) return [];
746
-
747
- if (typeof raw === 'string') {
748
- try {
749
- const parsed = JSON.parse(raw);
750
- return isSingle ? [parsed] : parsed;
751
- } catch {
752
- throw new BadRequestException('Invalid JSON in filter input');
753
- }
754
- }
755
-
756
- return isSingle ? [raw] : Array.isArray(raw) ? raw : [];
757
- }
758
-
759
- private async getSavedFilters(code?: string): Promise<any[]> {
760
- if (!code) return [];
761
-
762
- const savedFilter = await this.savedFilterService.getDetailsByCode(code);
763
- if (!savedFilter) {
764
- throw new BadRequestException(`Saved filter not found for code: ${code}`);
765
- }
766
- return savedFilter;
767
- }
768
-
769
- private buildWhereClauses(
770
- filters: any[],
771
- attributeMeta: Record<string, any>,
772
- ) {
773
- return filters
774
- .map((f) => {
775
- const clause = this.buildCondition(
776
- f,
777
- attributeMeta[f.filter_attribute],
778
- );
779
-
780
- if (!clause) return null;
781
-
782
- // Force every param to be a string
783
- if (clause.params) {
784
- Object.keys(clause.params).forEach((k) => {
785
- const val = clause.params[k];
786
- if (!Array.isArray(val)) {
787
- clause.params[k] = String(val); // only convert scalar values
788
- }
789
- });
790
- }
791
-
792
- return clause;
793
- })
794
- .filter(
795
- (clause): clause is { query: string; params: Record<string, string> } =>
796
- clause !== null,
797
- );
798
- }
799
-
800
- private buildCondition(
801
- filter: any,
802
- meta: any,
803
- ): { query: string; params: any } | null {
804
- if (!meta) return null;
805
-
806
- let attr = filter.filter_attribute;
807
- const val = filter.filter_value;
808
- const op = filter.filter_operator;
809
- const key = `param_${attr}_${Math.random().toString(36).substring(2, 8)}`;
810
-
811
- // if (
812
- // (meta.data_source_type === 'entity' ||
813
- // meta.data_source_type === 'master') &&
814
- // !filter.skip_id
815
- // ) {
816
- // attr = `${attr}_id`;
817
- // }
818
-
819
- switch (meta.data_type) {
820
- case 'text':
821
- return this.buildTextCondition(attr, op, val, key);
822
- case 'number':
823
- return this.buildNumberCondition(attr, op, val, key);
824
- case 'date':
825
- return this.buildDateCondition(attr, op, val, key);
826
- case 'select':
827
- return this.buildMultiSelectCondition(attr, op, val, key);
828
- case 'multiselect':
829
- return this.buildMultiSelectCondition(attr, op, val, key);
830
- case 'checkbox':
831
- return this.buildMultiSelectCondition(attr, op, val, key);
832
- case 'radio':
833
- return this.buildMultiSelectCondition(attr, op, val, key);
834
- case 'year':
835
- return this.buildYearCondition(attr, op, val, key);
836
- default:
837
- return null;
838
- }
839
- }
840
-
841
- private buildTextCondition(attr: string, op: string, val: any, key: string) {
842
- switch (op) {
843
- case 'contains':
844
- return {
845
- query: `LOWER(e.${attr}) LIKE :${key}`,
846
- params: { [key]: `%${val ? val.toLowerCase() : ''}%` },
847
- };
848
- case 'equal':
849
- return {
850
- query: `e.${attr} = :${key}`,
851
- params: { [key]: val },
852
- };
853
- case 'not_equal':
854
- return {
855
- query: `e.${attr} != :${key}`,
856
- params: { [key]: val },
857
- };
858
- case 'empty':
859
- return {
860
- query: `e.${attr} IS NULL`,
861
- params: {},
862
- };
863
- case 'not_empty':
864
- return {
865
- query: `e.${attr} IS NOT NULL`,
866
- params: {},
867
- };
868
- default:
869
- throw new BadRequestException(`Unsupported operator for text: ${op}`);
870
- }
871
- }
872
-
873
- private buildNumberCondition(
874
- attr: string,
875
- op: string,
876
- val: any,
877
- key: string,
878
- ) {
879
- switch (op) {
880
- case 'equal':
881
- return { query: `e.${attr} = :${key}`, params: { [key]: val } };
882
- case 'not_equal':
883
- return { query: `e.${attr} != :${key}`, params: { [key]: val } };
884
- case 'greater_than':
885
- return { query: `e.${attr} > :${key}`, params: { [key]: val } };
886
- case 'less_than':
887
- return { query: `e.${attr} < :${key}`, params: { [key]: val } };
888
- case 'less_than_qual_to':
889
- return { query: `e.${attr} <= :${key}`, params: { [key]: val } };
890
- case 'greater_than_qual_to':
891
- return { query: `e.${attr} >= :${key}`, params: { [key]: val } };
892
- case 'empty':
893
- return { query: `e.${attr} IS NULL`, params: {} };
894
- case 'not_empty':
895
- return { query: `e.${attr} IS NOT NULL`, params: {} };
896
-
897
- default:
898
- throw new BadRequestException(`Unsupported operator for number: ${op}`);
899
- }
900
- }
901
-
902
- private buildDateCondition(attr: string, op: string, val: any, key: string) {
903
- const dateColumn = `DATE(e.${attr})`;
904
- const monthColumn = `DATE_TRUNC('month', e.${attr})`;
905
-
906
- // convert to number when needed
907
- const numVal = Number(val);
908
-
909
- // INSIDE buildDateCondition
910
- const subtractBusinessDays = (days: number): string => {
911
- let d = new Date();
912
- let count = 0;
913
-
914
- while (count < days) {
915
- d.setDate(d.getDate() - 1);
916
-
917
- const day = d.getDay(); // 0 = Sunday, 6 = Saturday
918
-
919
- if (day !== 0 && day !== 6) {
920
- count++;
921
- }
922
- }
923
-
924
- return d.toISOString().split('T')[0];
925
- };
926
-
927
- switch (op) {
928
- // ============================================
929
- // BASIC COMPARISONS
930
- // ============================================
931
- case 'equal':
932
- case 'is':
933
- return {
934
- query: `${dateColumn} = :${key}`,
935
- params: { [key]: val },
936
- };
937
-
938
- case 'before':
939
- case 'is_before':
940
- return {
941
- query: `${dateColumn} < :${key}`,
942
- params: { [key]: val },
943
- };
944
-
945
- case 'after':
946
- case 'is_after':
947
- return {
948
- query: `${dateColumn} > :${key}`,
949
- params: { [key]: val },
950
- };
951
-
952
- case 'is_on_or_before':
953
- return {
954
- query: `${dateColumn} <= :${key}`,
955
- params: { [key]: val },
956
- };
957
-
958
- case 'is_on_or_after':
959
- return {
960
- query: `${dateColumn} >= :${key}`,
961
- params: { [key]: val },
962
- };
963
-
964
- // ============================================
965
- // EMPTY / NOT EMPTY
966
- // ============================================
967
- case 'empty':
968
- return {
969
- query: `e.${attr} IS NULL`,
970
- params: {},
971
- };
972
-
973
- case 'not_empty':
974
- return {
975
- query: `e.${attr} IS NOT NULL`,
976
- params: {},
977
- };
978
-
979
- // ============================================
980
- // DAY OFFSET LOGIC (ALWAYS ADJUST -1 DAY)
981
- // ============================================
982
- case 'is_day_before':
983
- if (isNaN(numVal)) {
984
- throw new BadRequestException(
985
- 'Value must be a number for is_day_before',
986
- );
987
- }
988
-
989
- const dayBefore = (() => {
990
- const d = new Date();
991
- d.setDate(d.getDate() - numVal);
992
-
993
- // Format as YYYY-MM-DD in IST
994
- return d.toLocaleDateString('en-CA', { timeZone: 'Asia/Kolkata' });
995
- })();
996
-
997
- return {
998
- query: `${dateColumn} <= :${key}`,
999
- params: { [key]: dayBefore },
1000
- };
1001
-
1002
- case 'is_day_after':
1003
- if (isNaN(numVal)) {
1004
- throw new BadRequestException(
1005
- 'Value must be a number for is_day_after',
1006
- );
1007
- }
1008
-
1009
- const dayAfter = (() => {
1010
- const d = new Date();
1011
- d.setDate(d.getDate() - 1 + numVal); // -1 because DB stores previous day
1012
- return d.toISOString().split('T')[0];
1013
- })();
1014
-
1015
- return {
1016
- query: `${dateColumn} > :${key}`,
1017
- params: { [key]: dayAfter },
1018
- };
1019
-
1020
- // ============================================
1021
- // MONTH OFFSET LOGIC
1022
- // ============================================
1023
- case 'is_month_before':
1024
- if (isNaN(numVal)) {
1025
- throw new BadRequestException(
1026
- 'Value must be a number for is_month_before',
1027
- );
1028
- }
1029
-
1030
- const monthBefore = (() => {
1031
- const d = new Date();
1032
- d.setMonth(d.getMonth() - numVal);
1033
- d.setDate(1);
1034
- d.setDate(d.getDate() - 1);
1035
- return d.toISOString().split('T')[0];
1036
- })();
1037
-
1038
- return {
1039
- query: `${monthColumn} < DATE_TRUNC('month', (:${key})::date)`,
1040
- params: { [key]: monthBefore },
1041
- };
1042
-
1043
- case 'is_month_after':
1044
- if (isNaN(numVal)) {
1045
- throw new BadRequestException(
1046
- 'Value must be a number for is_month_after',
1047
- );
1048
- }
1049
-
1050
- const monthAfter = (() => {
1051
- const d = new Date();
1052
- d.setMonth(d.getMonth() + numVal);
1053
- d.setDate(1);
1054
- d.setDate(d.getDate() - 1);
1055
- return d.toISOString().split('T')[0];
1056
- })();
1057
-
1058
- return {
1059
- query: `${monthColumn} > DATE_TRUNC('month', (:${key})::date)`,
1060
- params: { [key]: monthAfter },
1061
- };
1062
-
1063
- // ===== BETWEEN =====
1064
- case 'between': {
1065
- if (typeof val === 'string') {
1066
- val = val.split(',').map((v) => v.trim());
1067
- }
1068
-
1069
- if (
1070
- !Array.isArray(val) ||
1071
- val.length !== 2 ||
1072
- val[0] === '' ||
1073
- val[1] === ''
1074
- ) {
1075
- console.log(`Invalid value for between: ${val}`);
1076
- return null;
1077
- }
1078
-
1079
- return {
1080
- query: `${dateColumn} BETWEEN :${key}_start AND :${key}_end`,
1081
- params: {
1082
- [`${key}_start`]: val[0],
1083
- [`${key}_end`]: val[1],
1084
- },
1085
- };
1086
- }
1087
-
1088
- // ===== TODAY =====
1089
- case 'today': {
1090
- const today = moment().format('YYYY-MM-DD');
1091
- return {
1092
- query: `${dateColumn} = :today`,
1093
- params: { today },
1094
- };
1095
- }
1096
-
1097
- // ============================================
1098
- // BUSINESS DAY OFFSET LOGIC (SKIPS WEEKENDS)
1099
- // ALWAYS ADJUST -1 DAY FOR DB SHIFT
1100
- // ============================================
1101
-
1102
- case 'is_before_business_days': {
1103
- if (isNaN(numVal)) {
1104
- throw new BadRequestException('Value must be a number');
1105
- }
1106
-
1107
- const targetDate = subtractBusinessDays(numVal);
1108
-
1109
- return {
1110
- query: `${dateColumn} <= :${key} AND EXTRACT(DOW FROM ${dateColumn}) NOT IN (0, 6)`,
1111
- params: { [key]: targetDate },
1112
- };
1113
- }
1114
-
1115
- case 'is_after_business_days': {
1116
- if (isNaN(numVal)) {
1117
- throw new BadRequestException(
1118
- 'Value must be a number for is_after_business_days',
1119
- );
1120
- }
1121
-
1122
- const businessAfter = (() => {
1123
- let d = new Date();
1124
- let count = 0;
1125
-
1126
- while (count < numVal) {
1127
- d.setDate(d.getDate() + 1);
1128
- const day = d.getDay(); // 0=Sun, 6=Sat
1129
- if (day !== 0 && day !== 6) count++;
1130
- }
1131
-
1132
- // DB shift -1 day
1133
- d.setDate(d.getDate() - 1);
1134
-
1135
- return d.toISOString().split('T')[0];
1136
- })();
1137
-
1138
- return {
1139
- query: `${dateColumn} > :${key}`,
1140
- params: { [key]: businessAfter },
1141
- };
1142
- }
1143
-
1144
- // ============================================
1145
- // IN LAST DAY (range: today to N days before)
1146
- // ============================================
1147
- case 'in_last_day': {
1148
- if (isNaN(numVal)) {
1149
- throw new BadRequestException(
1150
- 'Value must be a number for in_last_day',
1151
- );
1152
- }
1153
-
1154
- const today = (() => {
1155
- const d = new Date();
1156
- return d.toISOString().split('T')[0];
1157
- })();
1158
-
1159
- const lastN = (() => {
1160
- const d = new Date();
1161
- d.setDate(d.getDate() - numVal);
1162
- return d.toISOString().split('T')[0];
1163
- })();
1164
-
1165
- return {
1166
- query: `${dateColumn} BETWEEN :${key}_start AND :${key}_end`,
1167
- params: {
1168
- [`${key}_start`]: lastN,
1169
- [`${key}_end`]: today,
1170
- },
1171
- };
1172
- }
1173
-
1174
- // ============================================
1175
- // IN NEXT DAY (range: today to N days after)
1176
- // ============================================
1177
- case 'in_next_day': {
1178
- if (isNaN(numVal)) {
1179
- throw new BadRequestException(
1180
- 'Value must be a number for in_next_day',
1181
- );
1182
- }
1183
-
1184
- const today = (() => {
1185
- const d = new Date();
1186
- return d.toISOString().split('T')[0];
1187
- })();
1188
-
1189
- const nextN = (() => {
1190
- const d = new Date();
1191
- d.setDate(d.getDate() + numVal);
1192
- return d.toISOString().split('T')[0];
1193
- })();
1194
-
1195
- return {
1196
- query: `${dateColumn} BETWEEN :${key}_start AND :${key}_end`,
1197
- params: {
1198
- [`${key}_start`]: today,
1199
- [`${key}_end`]: nextN,
1200
- },
1201
- };
1202
- }
1203
-
1204
- default:
1205
- throw new BadRequestException(`Unsupported operator for date: ${op}`);
1206
- }
1207
- }
1208
-
1209
- private buildSelectCondition(
1210
- attr: string,
1211
- op: string,
1212
- val: any,
1213
- key: string,
1214
- ) {
1215
- switch (op) {
1216
- case 'equal':
1217
- return { query: `e.${attr} = :${key}`, params: { [key]: val } };
1218
- case 'not_equal':
1219
- return { query: `e.${attr} != :${key}`, params: { [key]: val } };
1220
- case 'empty':
1221
- return { query: `e.${attr} IS NULL`, params: {} };
1222
- case 'not_empty':
1223
- return { query: `e.${attr} IS NOT NULL`, params: {} };
1224
- default:
1225
- throw new BadRequestException(`Unsupported operator for select: ${op}`);
1226
- }
1227
- }
1228
-
1229
- private buildMultiSelectCondition(
1230
- attr: string,
1231
- op: string,
1232
- val: any,
1233
- key: string,
1234
- ) {
1235
- let arr: string[] = [];
1236
-
1237
- // SAFETY: Convert all to array
1238
- if (Array.isArray(val)) {
1239
- arr = val.map((v) => String(v));
1240
- } else if (typeof val === 'string') {
1241
- arr = val.split(',').map((v) => v.trim());
1242
- } else {
1243
- throw new BadRequestException(`Invalid multiselect value`);
1244
- }
1245
-
1246
- if (arr.length === 0) {
1247
- return { query: '1=1', params: {} };
1248
- }
1249
-
1250
- if (op === 'equal') {
1251
- return {
1252
- query: `e.${attr}::text IN (:...${key})`,
1253
- params: { [key]: arr },
1254
- };
1255
- }
1256
-
1257
- if (op === 'not_equal') {
1258
- return {
1259
- query: `e.${attr}::text NOT IN (:...${key})`,
1260
- params: { [key]: arr },
1261
- };
1262
- }
1263
-
1264
- if (op === 'contains') {
1265
- return {
1266
- query: `e.${attr}::text LIKE :${key}`,
1267
- params: { [key]: `%${val}%` },
1268
- };
1269
- }
1270
-
1271
- if (op === 'not_contains') {
1272
- return {
1273
- query: `e.${attr}::text NOT LIKE :${key}`,
1274
- params: { [key]: `%${val}%` },
1275
- };
1276
- }
1277
-
1278
- if (op === 'empty') return { query: `e.${attr} IS NULL`, params: {} };
1279
- if (op === 'not_empty')
1280
- return { query: `e.${attr} IS NOT NULL`, params: {} };
1281
-
1282
- throw new BadRequestException(`Unsupported operator: ${op}`);
1283
- }
1284
-
1285
- private buildYearCondition(attr: string, op: string, val: any, key: string) {
1286
- switch (op) {
1287
- case 'equal':
1288
- return {
1289
- query: `e.${attr} = :${key}`,
1290
- params: { [key]: parseInt(val, 10) },
1291
- };
1292
- case 'not_equal':
1293
- return {
1294
- query: `e.${attr} != :${key}`,
1295
- params: { [key]: parseInt(val, 10) },
1296
- };
1297
- case 'greater_than':
1298
- return {
1299
- query: `e.${attr} > :${key}`,
1300
- params: { [key]: parseInt(val, 10) },
1301
- };
1302
- case 'less_than':
1303
- return {
1304
- query: `e.${attr} < :${key}`,
1305
- params: { [key]: parseInt(val, 10) },
1306
- };
1307
- default:
1308
- throw new BadRequestException(`Unsupported operator for year: ${op}`);
1309
- }
1310
- }
1311
-
1312
- async queryWithSchema(sql: string, params: any[] = []) {
1313
- await this.entityManager.query('BEGIN');
1314
- await this.entityManager.query(`SET LOCAL search_path TO ${this.schema}`);
1315
- const result = await this.entityManager.query(sql, params);
1316
- await this.entityManager.query('COMMIT');
1317
- return result;
1318
- }
1319
- }
1
+ import { BadRequestException, Inject, Injectable } from '@nestjs/common';
2
+ import { AttributeMasterService } from 'src/module/meta/service/attribute-master.service';
3
+ import { EntityMasterService } from 'src/module/meta/service/entity-master.service';
4
+ import { FilterCondition, FilterRequestDto } from '../dto/filter-request.dto';
5
+ import { SavedFilterService } from './saved-filter.service';
6
+
7
+ import * as moment from 'moment';
8
+ import { EntityRelationService } from 'src/module/meta/service/entity-relation.service';
9
+ import { ResolverService } from 'src/module/meta/service/resolver.service';
10
+ import { LoggingService } from 'src/utils/service/loggingUtil.service';
11
+ import { ConfigService } from '@nestjs/config';
12
+ import { ReflectionHelper } from '../../../utils/service/reflection-helper.service';
13
+ import { EntityManager } from 'typeorm';
14
+
15
+ @Injectable()
16
+ export class FilterService {
17
+ constructor(
18
+ private entityManager: EntityManager,
19
+ private readonly attributeMasterService: AttributeMasterService,
20
+ private readonly entityMasterService: EntityMasterService,
21
+ @Inject('SavedFilterService')
22
+ private readonly savedFilterService: SavedFilterService,
23
+ @Inject('EntityRelationService')
24
+ private readonly entityRelationService: EntityRelationService,
25
+ private readonly resolverService: ResolverService,
26
+ @Inject() protected readonly loggingService: LoggingService,
27
+ private readonly configService: ConfigService,
28
+ private readonly reflectionHelper: ReflectionHelper,
29
+ ) {
30
+ }
31
+
32
+ schema = this.configService.get('DB_SCHEMA');
33
+
34
+ private async gettab_value_counts(
35
+ tableName: string,
36
+ column: string | undefined,
37
+ whereClauses: { query: string; params: Record<string, any> }[],
38
+ ) {
39
+ if (!column) return [];
40
+
41
+ let whereSQL = '';
42
+ const values: any[] = [];
43
+ let paramIndex = 1; // PostgreSQL placeholders start at $1
44
+
45
+ if (whereClauses.length > 0) {
46
+ const clauseParts = whereClauses.map((clause) => {
47
+ let parsedQuery = clause.query.replace(/\be\./g, ''); // remove e.
48
+
49
+ Object.entries(clause.params).forEach(([key, val]) => {
50
+ if (Array.isArray(val)) {
51
+ // Create ($1,$2,$3)
52
+ const placeholders = val.map(() => `$${paramIndex++}`).join(', ');
53
+
54
+ parsedQuery = parsedQuery.replace(
55
+ new RegExp(`:${key}`, 'g'),
56
+ `(${placeholders})`,
57
+ );
58
+
59
+ values.push(...val.map((v) => String(v)));
60
+ } else {
61
+ // Create $1
62
+ const placeholder = `$${paramIndex++}`;
63
+
64
+ parsedQuery = parsedQuery.replace(
65
+ new RegExp(`:${key}`, 'g'),
66
+ placeholder,
67
+ );
68
+
69
+ values.push(String(val));
70
+ }
71
+ });
72
+
73
+ return parsedQuery;
74
+ });
75
+
76
+ whereSQL = `WHERE ${clauseParts.join(' AND ')}`;
77
+ }
78
+
79
+ const rawSQL = `
80
+ SELECT ${column} AS tab_value, COUNT(*) AS tab_value_count
81
+ FROM ${this.schema}.${tableName} ${whereSQL}
82
+ GROUP BY ${column}
83
+ `;
84
+
85
+
86
+ const rows = await this.entityManager.query(rawSQL, values);
87
+
88
+ const total = rows.reduce(
89
+ (sum, r) => sum + parseInt(r.tab_value_count, 10),
90
+ 0,
91
+ );
92
+
93
+ return [
94
+ { tab_value: 'All', tab_value_count: total },
95
+ ...rows.map((r) => ({
96
+ tab_value: r.tab_value ?? 'BLANK',
97
+ tab_value_count: parseInt(r.tab_value_count, 10),
98
+ })),
99
+ ];
100
+ }
101
+
102
+ async applyFilterWrapper(dto: FilterRequestDto) {
103
+ const {
104
+ entity_type,
105
+ quickFilter = [],
106
+ savedFilterCode,
107
+ attributeFilter = [],
108
+ loggedInUser,
109
+ } = dto;
110
+
111
+ // 🔹 Step 1: Collect all filters (from body + savedFilter)
112
+ const savedFilters = await this.getSavedFilters(
113
+ savedFilterCode ?? undefined,
114
+ );
115
+ const allFilters = [
116
+ ...quickFilter,
117
+ ...attributeFilter,
118
+ ...savedFilters,
119
+ ].filter((f) => f.filter_value !== '');
120
+
121
+ // 🔹 Step 2: Group filters by filter_entity_type
122
+ const grouped = allFilters.reduce(
123
+ (acc, f) => {
124
+ if (!acc[f.filter_entity_type]) acc[f.filter_entity_type] = [];
125
+ acc[f.filter_entity_type].push(f);
126
+ return acc;
127
+ },
128
+ {} as Record<string, any[]>,
129
+ );
130
+
131
+ console.log('🟠 [FilterService] Grouped filters by entity type:', grouped);
132
+
133
+ // 🔹 Step 3: Handle sub-entities first
134
+ let intersectionIds: number[] | null = null;
135
+ for (const [subEntityType, filters] of Object.entries(grouped)) {
136
+ if (subEntityType === entity_type) continue; // skip main entity for now
137
+
138
+ let { queryParams, tabs, ...newDto } = dto;
139
+
140
+ const subDto: FilterRequestDto = {
141
+ ...newDto,
142
+ entity_type: subEntityType,
143
+ quickFilter: filters as FilterCondition[],
144
+ savedFilterCode: null, // already merged
145
+ attributeFilter: [],
146
+ };
147
+
148
+ const subResult = await this.applyFilter(subDto);
149
+ const subEntityIds = subResult.data.entity_list.map((row) => row.id);
150
+
151
+ console.log(
152
+ `🧩 [FilterService] Sub-entity ${subEntityType} returned IDs:`,
153
+ subEntityIds,
154
+ );
155
+
156
+ if (!subEntityIds.length) {
157
+ console.log(
158
+ `ℹ️ [FilterService] No records for sub-entity ${subEntityType}, returning empty result`,
159
+ );
160
+ return {
161
+ success: true,
162
+ data: { entity_tabs: [], entity_list: [], pagination: {} },
163
+ };
164
+ }
165
+
166
+ const relatedIds = await this.entityRelationService.getRelatedEntityIds(
167
+ entity_type,
168
+ subEntityType,
169
+ subEntityIds,
170
+ dto.loggedInUser.enterprise_id,
171
+ );
172
+
173
+ intersectionIds =
174
+ intersectionIds === null
175
+ ? relatedIds
176
+ : intersectionIds.filter((id) => relatedIds.includes(id));
177
+
178
+ if (intersectionIds.length === 0) {
179
+ console.log(
180
+ '🚫 [FilterService] No intersection IDs left, returning empty result',
181
+ );
182
+ return {
183
+ success: true,
184
+ data: { entity_tabs: [], entity_list: [], pagination: {} },
185
+ };
186
+ }
187
+ }
188
+
189
+ // 🔹 Step 4: Call applyFilter for main entity
190
+ const mainFilters = grouped[entity_type] || [];
191
+ const mainDto: FilterRequestDto = {
192
+ ...dto,
193
+ quickFilter: [...mainFilters],
194
+ savedFilterCode: null,
195
+ attributeFilter: [],
196
+ };
197
+
198
+ if (intersectionIds && intersectionIds.length > 0) {
199
+ (mainDto.quickFilter ??= []).push({
200
+ filter_attribute: 'id',
201
+ filter_operator: 'equal',
202
+ filter_value: intersectionIds,
203
+ filter_entity_type: entity_type,
204
+ });
205
+ }
206
+
207
+ return await this.applyFilter(mainDto);
208
+ }
209
+
210
+ async applyFilter(dto: FilterRequestDto) {
211
+ const {
212
+ entity_type,
213
+ quickFilter,
214
+ savedFilterCode,
215
+ attributeFilter,
216
+ tabs,
217
+ sortby,
218
+ loggedInUser,
219
+ queryParams,
220
+ customLevelType,
221
+ customLevelId,
222
+ customAppCode,
223
+ } = dto;
224
+
225
+ // abstract user details
226
+ const {
227
+ level_type,
228
+ level_id,
229
+ id: user_id,
230
+ appcode,
231
+ organization_id,
232
+ } = loggedInUser || {};
233
+
234
+ // Fetch meta from entity table service
235
+ const entityMeta = await this.entityMasterService.getEntityData(
236
+ entity_type,
237
+ loggedInUser,
238
+ );
239
+ const tableName = entityMeta?.data_source; // data_source is the table name
240
+
241
+ if (!tableName) {
242
+ console.error(`❌ [FilterService] Invalid entity_type: ${entity_type}`);
243
+ throw new BadRequestException(`Invalid entity_type: ${entity_type}`);
244
+ }
245
+
246
+ const getAttributeColumnMeta =
247
+ await this.attributeMasterService.findAttributesByMappedEntityType(
248
+ entity_type,
249
+ loggedInUser,
250
+ );
251
+
252
+ const attributeMetaMap = getAttributeColumnMeta.reduce(
253
+ (acc, attr) => {
254
+ acc[attr.attribute_key] = attr;
255
+ return acc;
256
+ },
257
+ {} as Record<string, any>,
258
+ );
259
+
260
+ // Get and parse saved filters
261
+ const savedFilters = await this.getSavedFilters(
262
+ savedFilterCode ?? undefined,
263
+ );
264
+ const savedFiltersNormalized = savedFilters.map((f) => ({
265
+ filter_attribute: f.filter_attribute,
266
+ filter_operator: f.filter_operator,
267
+ filter_value: f.filter_value,
268
+ }));
269
+
270
+ const baseFilters = [
271
+ ...(quickFilter || []).filter(
272
+ (f) => f.filter_value != null && f.filter_value !== '',
273
+ ),
274
+ ...savedFiltersNormalized.filter(
275
+ (f) => f.filter_value != null && f.filter_value !== '',
276
+ ),
277
+ ...(attributeFilter || []).filter(
278
+ (f) => f.filter_value != null && f.filter_value !== '',
279
+ ),
280
+ ];
281
+
282
+ // 🧱 Build where clausesx
283
+ const baseWhere = this.buildWhereClauses(baseFilters, attributeMetaMap);
284
+
285
+ // Handle TEMPLATE entity special condition
286
+ if (entity_type === 'TEMP' || entity_type === 'TEM') {
287
+ const templateTable = entityMeta?.data_source || 'frm_wf_comm_template';
288
+
289
+ if (level_type === 'ORG') {
290
+ baseWhere.push({
291
+ query: ` ((e.level_type = 'ORG' AND e.level_id = '${loggedInUser.organization_id}' AND e.organization_id = '${loggedInUser.organization_id}'))`,
292
+ params: {},
293
+ });
294
+ } else if (level_type === 'SCH') {
295
+ baseWhere.push({
296
+ query: `
297
+ (
298
+ (e.level_type = 'SCH' AND e.level_id = '${loggedInUser.level_id}' AND e.organization_id = '${loggedInUser.organization_id}')
299
+ OR
300
+ (
301
+ e.level_type = 'ORG'
302
+ AND e.level_id = '${loggedInUser.organization_id}'
303
+ AND e.organization_id = '${loggedInUser.organization_id}'
304
+ AND e.code NOT IN (
305
+ SELECT sub.code
306
+ FROM frm_wf_comm_template AS sub
307
+ WHERE sub.level_type = 'SCH' AND sub.level_id = '${loggedInUser.level_id}'
308
+ )
309
+ )
310
+ )
311
+ `,
312
+ params: {},
313
+ });
314
+ }
315
+ }
316
+
317
+ // Default org/level clause — skip TEMPLATE entity
318
+ if (
319
+ entity_type !== 'ORGP' &&
320
+ entity_type !== 'TEMP' &&
321
+ entity_type !== 'TEM' && // ✅ skip TEMPLATE
322
+ level_type &&
323
+ level_id &&
324
+ organization_id &&
325
+ !customLevelType &&
326
+ !customLevelId
327
+ ) {
328
+ baseWhere.push({
329
+ query:
330
+ 'e.organization_id = :organization_id AND e.level_type = :level_type AND e.level_id = :level_id',
331
+ params: {
332
+ organization_id: String(organization_id),
333
+ level_type,
334
+ level_id,
335
+ },
336
+ });
337
+ }
338
+
339
+ if (entity_type == 'USR' || entity_type == 'UPR') {
340
+ baseWhere.push({
341
+ query: 'e.is_customer is NULL',
342
+ params: {},
343
+ });
344
+ }
345
+
346
+ if (customLevelType && customLevelId && customAppCode) {
347
+ baseWhere.push({
348
+ query:
349
+ 'e.organization_id = :organization_id AND e.level_type = :customLevelType AND e.level_id = :customLevelId AND e.appcode = :customAppCode',
350
+ params: {
351
+ organization_id: String(organization_id),
352
+ customLevelType,
353
+ customLevelId: String(customLevelId),
354
+ customAppCode,
355
+ },
356
+ });
357
+ }
358
+
359
+ // Handle queryParams filters
360
+ if (queryParams) {
361
+ Object.entries(queryParams).forEach(([key, value]) => {
362
+ if (!value) return;
363
+
364
+ // Always convert to string
365
+ const strValue = String(value);
366
+
367
+ if (key === 'attributeName' && queryParams['attributeValue']) {
368
+ const attrName = strValue;
369
+ const attrValue = String(queryParams['attributeValue']);
370
+
371
+ baseWhere.push({
372
+ query: `e.${attrName} = :${attrName}`,
373
+ params: { [attrName]: attrValue }, // <-- string
374
+ });
375
+ } else if (key !== 'attributeValue') {
376
+ baseWhere.push({
377
+ query: `e.${key} = :${key}`,
378
+ params: { [key]: strValue }, // <-- string
379
+ });
380
+ }
381
+ });
382
+ }
383
+
384
+ console.log('🟠 [FilterService] Constructed baseWhere clauses:', baseWhere);
385
+
386
+ let layoutPreferenceRepo =
387
+ this.reflectionHelper.getRepoService('LayoutPreference');
388
+
389
+ const layoutPreference = await layoutPreferenceRepo.findOne({
390
+ where: {
391
+ user_id: user_id,
392
+ mapped_entity_type: entity_type,
393
+ mapped_level_id: level_id,
394
+ mapped_level_type: level_type,
395
+ type: 'layout',
396
+ },
397
+ });
398
+
399
+ // Extract layout preference
400
+ const layout = layoutPreference?.mapped_json?.quick_tab || {};
401
+ const showList =
402
+ layout?.show_list?.map((val) => {
403
+ if (typeof val === 'string') {
404
+ // legacy case — old format like ['Active', 'Inactive']
405
+ return val.toLowerCase();
406
+ }
407
+
408
+ if (val && typeof val === 'object' && val.value !== undefined) {
409
+ // new format — [{ label: 'Active', value: '13023' }]
410
+ return String(val.value).toLowerCase();
411
+ }
412
+
413
+ return ''; // fallback safety
414
+ }) || [];
415
+
416
+ let filteredTabs = await this.getFilteredTabs(
417
+ layout,
418
+ showList,
419
+ entityMeta,
420
+ baseWhere,
421
+ tabs,
422
+ loggedInUser,
423
+ );
424
+
425
+ const dataWhere = [...baseWhere];
426
+
427
+ if (tabs?.columnName && tabs?.value) {
428
+ const tabAttrMeta = attributeMetaMap[tabs.columnName];
429
+ const tabValue = tabs.value.toLowerCase();
430
+
431
+ if (tabValue === 'others') {
432
+ // Extract 'value' (IDs) from showList, ignore 'all'
433
+ const valuesToExclude = showList
434
+ .filter((v) => v.label.toLowerCase() !== 'all')
435
+ .map((v) => v.value);
436
+
437
+ if (valuesToExclude.length > 0) {
438
+ for (const value of valuesToExclude) {
439
+ const resolvedId = await this.resolverService.getResolvedId(
440
+ loggedInUser,
441
+ tabs.columnName,
442
+ value,
443
+ entity_type,
444
+ );
445
+ if (resolvedId) {
446
+ const tabCondition = this.buildCondition(
447
+ {
448
+ filter_attribute: tabs.columnName,
449
+ filter_operator: 'not_equal',
450
+ filter_value: [resolvedId],
451
+ skip_id: true,
452
+ },
453
+ tabAttrMeta,
454
+ );
455
+ if (tabCondition) dataWhere.push(tabCondition);
456
+ }
457
+ }
458
+ }
459
+ } else if (tabValue !== 'all') {
460
+ const resolvedId = await this.resolverService.getResolvedId(
461
+ loggedInUser,
462
+ tabs.columnName,
463
+ tabs.value,
464
+ entity_type,
465
+ );
466
+
467
+ if (resolvedId) {
468
+ const tabCondition = this.buildCondition(
469
+ {
470
+ filter_attribute: tabs.columnName,
471
+ filter_operator: 'equal',
472
+ filter_value: [resolvedId],
473
+ skip_id: true,
474
+ },
475
+ tabAttrMeta,
476
+ );
477
+ if (tabCondition) dataWhere.push(tabCondition);
478
+ }
479
+ }
480
+ }
481
+
482
+ let qb = this.entityManager
483
+ .createQueryBuilder()
484
+ .select('e.*')
485
+ .from(`${this.schema}.${tableName}`, 'e');
486
+ dataWhere.forEach((clause) => qb.andWhere(clause.query, clause.params));
487
+
488
+ // Apply sorting
489
+ qb = await this.sortTabsByShowList(qb, sortby, layoutPreference, tabs);
490
+ const page = dto.page && dto.page > 0 ? dto.page : 1;
491
+ const size = dto.size && dto.size > 0 ? dto.size : 10;
492
+ if (dto.page && dto.size) {
493
+ qb.skip((page - 1) * size).take(size);
494
+ }
495
+
496
+ let query = await qb.getQuery();
497
+
498
+ console.log('------------------------------------------------------\n');
499
+ console.log(query);
500
+ console.log('\n------------------------------------------------------');
501
+ const entity_list = await qb.getRawMany();
502
+
503
+ console.log(`📦 [FilterService] Fetched ${entity_list.length} records`);
504
+
505
+ const dateAttributes = Object.entries(attributeMetaMap)
506
+ .filter(([_, attr]) => attr.data_type === 'date')
507
+ .map(([key]) => key);
508
+
509
+ const formatDate = (dateStr: string | null): string | null =>
510
+ dateStr ? moment(dateStr).format('DD-MM-YYYY') : '';
511
+
512
+ const formatDatesInRow = (row: any): any => {
513
+ const formattedRow = { ...row };
514
+ for (const key of dateAttributes) {
515
+ if (formattedRow[key])
516
+ formattedRow[key] = formatDate(formattedRow[key]);
517
+ }
518
+ return formattedRow;
519
+ };
520
+
521
+ const formattedEntityList = entity_list.map(formatDatesInRow);
522
+
523
+ const resolvedEntityList = await Promise.all(
524
+ formattedEntityList.map((row) =>
525
+ this.resolverService.getResolvedData(loggedInUser, row, entity_type),
526
+ ),
527
+ );
528
+
529
+ const resolvedTabs = await Promise.all(
530
+ filteredTabs.map(async (tab) => {
531
+ const tabAttrKey = layout?.attribute || tabs?.columnName;
532
+
533
+ if (
534
+ !tabAttrKey ||
535
+ tab.tab_value?.toLowerCase() === 'all' ||
536
+ tab.tab_value?.toLowerCase() === 'others'
537
+ ) {
538
+ return tab;
539
+ }
540
+
541
+ const resolvedVal = await this.resolverService.getResolvedValue(
542
+ loggedInUser,
543
+ tabAttrKey,
544
+ tab.tab_value,
545
+ entity_type,
546
+ );
547
+
548
+ return { ...tab, tab_value: resolvedVal ?? tab.tab_value };
549
+ }),
550
+ );
551
+
552
+ const countQb = this.entityManager
553
+ .createQueryBuilder()
554
+ .select('COUNT(*)', 'count')
555
+ .from(`${this.schema}.${tableName}`, 'e');
556
+ dataWhere.forEach((clause) =>
557
+ countQb.andWhere(clause.query, clause.params),
558
+ );
559
+ const countResult = await countQb.getRawOne();
560
+ const total = parseInt(countResult.count, 10);
561
+
562
+ console.log('📊 [FilterService] Returning final result with total:', total);
563
+
564
+ return {
565
+ success: true,
566
+ data: {
567
+ entity_tabs: resolvedTabs,
568
+ entity_list: resolvedEntityList,
569
+ pagination: {
570
+ total,
571
+ page,
572
+ size,
573
+ totalPages: Math.ceil(total / size),
574
+ hasNextPage: page * size < total,
575
+ hasPreviousPage: page > 1,
576
+ },
577
+ },
578
+ };
579
+ }
580
+
581
+ // GET FILTERED TABS LOGIC
582
+ private async getFilteredTabs(
583
+ layout: any,
584
+ showList: any,
585
+ entityMeta: any,
586
+ baseWhere: any,
587
+ tabs: any,
588
+ loggedInUser: any,
589
+ ) {
590
+ let allTabs;
591
+ if (layout.attribute) {
592
+ allTabs = await this.gettab_value_counts(
593
+ entityMeta?.data_source,
594
+ layout.attribute,
595
+ baseWhere,
596
+ );
597
+ } else {
598
+ allTabs = await this.gettab_value_counts(
599
+ entityMeta?.data_source,
600
+ tabs?.columnName,
601
+ baseWhere,
602
+ );
603
+ }
604
+
605
+ let filteredTabs: any[] = [];
606
+
607
+ if (showList?.length > 0) {
608
+ // Extract tab IDs (values)
609
+ const showValues = (showList || []).map((item) => {
610
+ const val = typeof item === 'object' ? item.value : item;
611
+ return val ? String(val).toLowerCase() : '';
612
+ });
613
+
614
+ // Handle "all" logic
615
+ const isAllNeeded = layout?.isAllSelected && !showValues.includes('all');
616
+ if (isAllNeeded) {
617
+ showList.push({ label: 'All', value: 'all' });
618
+ showValues.push('all');
619
+ }
620
+
621
+ // Filter by value IDs (not labels)
622
+ filteredTabs = allTabs.filter((tab) => {
623
+ const tabValueStr = tab.tab_value
624
+ ? String(tab.tab_value).toLowerCase()
625
+ : '';
626
+ return showValues.includes(tabValueStr);
627
+ });
628
+
629
+ // Handle "all" tab
630
+ const allTab = filteredTabs.find(
631
+ (tab) => tab.tab_value?.toString().toLowerCase() === 'all',
632
+ );
633
+ filteredTabs = filteredTabs.filter(
634
+ (tab) => tab.tab_value?.toString().toLowerCase() !== 'all',
635
+ );
636
+
637
+ // SORTING LOGIC
638
+ if (layout.sorting === 'asc') {
639
+ filteredTabs.sort((a, b) =>
640
+ a.tab_value.toString().localeCompare(b.tab_value.toString()),
641
+ );
642
+ } else if (layout.sorting === 'dsc') {
643
+ filteredTabs.sort((a, b) =>
644
+ b.tab_value.toString().localeCompare(a.tab_value.toString()),
645
+ );
646
+ } else if (layout.sorting === 'count_asc') {
647
+ filteredTabs.sort((a, b) => a.tab_value_count - b.tab_value_count);
648
+ } else if (layout.sorting === 'count_dsc') {
649
+ filteredTabs.sort((a, b) => b.tab_value_count - a.tab_value_count);
650
+ } else if (layout.sorting === 'custom') {
651
+ // Keep order same as showList
652
+ const orderMap = new Map<string, number>(
653
+ showList.map((item, index) => [
654
+ item.value?.toString().toLowerCase(),
655
+ index,
656
+ ]),
657
+ );
658
+ filteredTabs.sort((a, b) => {
659
+ const aIndex =
660
+ orderMap.get(a.tab_value?.toString().toLowerCase()) ?? Infinity;
661
+ const bIndex =
662
+ orderMap.get(b.tab_value?.toString().toLowerCase()) ?? Infinity;
663
+ return aIndex - bIndex;
664
+ });
665
+ }
666
+
667
+ // Re-add "all" tab at beginning if needed
668
+ if (allTab) filteredTabs.unshift(allTab);
669
+
670
+ // Combine others if enabled
671
+ if (layout?.isCombineOther) {
672
+ const originalAllTab = allTabs.find(
673
+ (tab) => tab.tab_value?.toString().toLowerCase() === 'all',
674
+ );
675
+ const allCount = originalAllTab?.tab_value_count ?? 0;
676
+ const knownTabCountSum = filteredTabs
677
+ .filter((tab) => tab.tab_value?.toString().toLowerCase() !== 'all')
678
+ .reduce((acc, tab) => acc + tab.tab_value_count, 0);
679
+
680
+ const othersCount = allCount - knownTabCountSum;
681
+ filteredTabs.push({
682
+ tab_value: 'OTHERS',
683
+ tab_value_count: othersCount < 0 ? 0 : othersCount,
684
+ });
685
+ }
686
+ } else {
687
+ filteredTabs = allTabs;
688
+ }
689
+
690
+ return filteredTabs;
691
+ }
692
+
693
+ // SORTING LOGIC FOR TABS
694
+ private async sortTabsByShowList(
695
+ qb: any,
696
+ sortby: any,
697
+ layoutPreference: any,
698
+ tabs: any,
699
+ ) {
700
+ if (layoutPreference && layoutPreference[0]?.mapped_json?.sorting) {
701
+ if (Array.isArray(layoutPreference[0]?.mapped_json?.sorting?.tabs)) {
702
+ const preferenceTabArray =
703
+ layoutPreference[0]?.mapped_json?.sorting?.tabs;
704
+ const tabFilter = preferenceTabArray.find(
705
+ (tabData) => tabData.tab_name === tabs?.value,
706
+ );
707
+ tabFilter?.sortby.forEach(({ column, order }) => {
708
+ qb.addOrderBy(
709
+ `e.${column}`,
710
+ order?.toUpperCase() === 'DSC' ? 'DESC' : 'ASC',
711
+ );
712
+ });
713
+ } else if (
714
+ Array.isArray(layoutPreference[0]?.mapped_json?.sorting?.sortby)
715
+ ) {
716
+ layoutPreference[0]?.mapped_json?.sorting?.sortby?.forEach(
717
+ ({ column, order }) => {
718
+ qb.addOrderBy(
719
+ `e.${column}`,
720
+ order?.toUpperCase() === 'DSC' ? 'DESC' : 'ASC',
721
+ );
722
+ },
723
+ );
724
+ }
725
+ }
726
+
727
+ if (Array.isArray(sortby) && sortby.length > 0) {
728
+ sortby.forEach(({ sortColum, sortType }) => {
729
+ if (sortColum) {
730
+ qb.addOrderBy(
731
+ `e.${sortColum}`,
732
+ sortType?.toUpperCase() === 'DSC' ? 'DESC' : 'ASC',
733
+ );
734
+ }
735
+ });
736
+ } else {
737
+ // fallback if no explicit sortby sent
738
+ qb.addOrderBy('e.created_date', 'DESC');
739
+ }
740
+
741
+ return qb;
742
+ }
743
+
744
+ private parseFilters(raw: any, isSingle = false): any[] {
745
+ if (!raw) return [];
746
+
747
+ if (typeof raw === 'string') {
748
+ try {
749
+ const parsed = JSON.parse(raw);
750
+ return isSingle ? [parsed] : parsed;
751
+ } catch {
752
+ throw new BadRequestException('Invalid JSON in filter input');
753
+ }
754
+ }
755
+
756
+ return isSingle ? [raw] : Array.isArray(raw) ? raw : [];
757
+ }
758
+
759
+ private async getSavedFilters(code?: string): Promise<any[]> {
760
+ if (!code) return [];
761
+
762
+ const savedFilter = await this.savedFilterService.getDetailsByCode(code);
763
+ if (!savedFilter) {
764
+ throw new BadRequestException(`Saved filter not found for code: ${code}`);
765
+ }
766
+ return savedFilter;
767
+ }
768
+
769
+ private buildWhereClauses(
770
+ filters: any[],
771
+ attributeMeta: Record<string, any>,
772
+ ) {
773
+ return filters
774
+ .map((f) => {
775
+ const clause = this.buildCondition(
776
+ f,
777
+ attributeMeta[f.filter_attribute],
778
+ );
779
+
780
+ if (!clause) return null;
781
+
782
+ // Force every param to be a string
783
+ if (clause.params) {
784
+ Object.keys(clause.params).forEach((k) => {
785
+ const val = clause.params[k];
786
+ if (!Array.isArray(val)) {
787
+ clause.params[k] = String(val); // only convert scalar values
788
+ }
789
+ });
790
+ }
791
+
792
+ return clause;
793
+ })
794
+ .filter(
795
+ (clause): clause is { query: string; params: Record<string, string> } =>
796
+ clause !== null,
797
+ );
798
+ }
799
+
800
+ private buildCondition(
801
+ filter: any,
802
+ meta: any,
803
+ ): { query: string; params: any } | null {
804
+ if (!meta) return null;
805
+
806
+ let attr = filter.filter_attribute;
807
+ const val = filter.filter_value;
808
+ const op = filter.filter_operator;
809
+ const key = `param_${attr}_${Math.random().toString(36).substring(2, 8)}`;
810
+
811
+ // if (
812
+ // (meta.data_source_type === 'entity' ||
813
+ // meta.data_source_type === 'master') &&
814
+ // !filter.skip_id
815
+ // ) {
816
+ // attr = `${attr}_id`;
817
+ // }
818
+
819
+ switch (meta.data_type) {
820
+ case 'text':
821
+ return this.buildTextCondition(attr, op, val, key);
822
+ case 'number':
823
+ return this.buildNumberCondition(attr, op, val, key);
824
+ case 'date':
825
+ return this.buildDateCondition(attr, op, val, key);
826
+ case 'select':
827
+ return this.buildMultiSelectCondition(attr, op, val, key);
828
+ case 'multiselect':
829
+ return this.buildMultiSelectCondition(attr, op, val, key);
830
+ case 'checkbox':
831
+ return this.buildMultiSelectCondition(attr, op, val, key);
832
+ case 'radio':
833
+ return this.buildMultiSelectCondition(attr, op, val, key);
834
+ case 'year':
835
+ return this.buildYearCondition(attr, op, val, key);
836
+ default:
837
+ return null;
838
+ }
839
+ }
840
+
841
+ private buildTextCondition(attr: string, op: string, val: any, key: string) {
842
+ switch (op) {
843
+ case 'contains':
844
+ return {
845
+ query: `LOWER(e.${attr}) LIKE :${key}`,
846
+ params: { [key]: `%${val ? val.toLowerCase() : ''}%` },
847
+ };
848
+ case 'equal':
849
+ return {
850
+ query: `e.${attr} = :${key}`,
851
+ params: { [key]: val },
852
+ };
853
+ case 'not_equal':
854
+ return {
855
+ query: `e.${attr} != :${key}`,
856
+ params: { [key]: val },
857
+ };
858
+ case 'empty':
859
+ return {
860
+ query: `e.${attr} IS NULL`,
861
+ params: {},
862
+ };
863
+ case 'not_empty':
864
+ return {
865
+ query: `e.${attr} IS NOT NULL`,
866
+ params: {},
867
+ };
868
+ default:
869
+ throw new BadRequestException(`Unsupported operator for text: ${op}`);
870
+ }
871
+ }
872
+
873
+ private buildNumberCondition(
874
+ attr: string,
875
+ op: string,
876
+ val: any,
877
+ key: string,
878
+ ) {
879
+ switch (op) {
880
+ case 'equal':
881
+ return { query: `e.${attr} = :${key}`, params: { [key]: val } };
882
+ case 'not_equal':
883
+ return { query: `e.${attr} != :${key}`, params: { [key]: val } };
884
+ case 'greater_than':
885
+ return { query: `e.${attr} > :${key}`, params: { [key]: val } };
886
+ case 'less_than':
887
+ return { query: `e.${attr} < :${key}`, params: { [key]: val } };
888
+ case 'less_than_qual_to':
889
+ return { query: `e.${attr} <= :${key}`, params: { [key]: val } };
890
+ case 'greater_than_qual_to':
891
+ return { query: `e.${attr} >= :${key}`, params: { [key]: val } };
892
+ case 'empty':
893
+ return { query: `e.${attr} IS NULL`, params: {} };
894
+ case 'not_empty':
895
+ return { query: `e.${attr} IS NOT NULL`, params: {} };
896
+
897
+ default:
898
+ throw new BadRequestException(`Unsupported operator for number: ${op}`);
899
+ }
900
+ }
901
+
902
+ private buildDateCondition(attr: string, op: string, val: any, key: string) {
903
+ const dateColumn = `DATE(e.${attr})`;
904
+ const monthColumn = `DATE_TRUNC('month', e.${attr})`;
905
+
906
+ // convert to number when needed
907
+ const numVal = Number(val);
908
+
909
+ // INSIDE buildDateCondition
910
+ const subtractBusinessDays = (days: number): string => {
911
+ let d = new Date();
912
+ let count = 0;
913
+
914
+ while (count < days) {
915
+ d.setDate(d.getDate() - 1);
916
+
917
+ const day = d.getDay(); // 0 = Sunday, 6 = Saturday
918
+
919
+ if (day !== 0 && day !== 6) {
920
+ count++;
921
+ }
922
+ }
923
+
924
+ return d.toISOString().split('T')[0];
925
+ };
926
+
927
+ switch (op) {
928
+ // ============================================
929
+ // BASIC COMPARISONS
930
+ // ============================================
931
+ case 'equal':
932
+ case 'is':
933
+ return {
934
+ query: `${dateColumn} = :${key}`,
935
+ params: { [key]: val },
936
+ };
937
+
938
+ case 'before':
939
+ case 'is_before':
940
+ return {
941
+ query: `${dateColumn} < :${key}`,
942
+ params: { [key]: val },
943
+ };
944
+
945
+ case 'after':
946
+ case 'is_after':
947
+ return {
948
+ query: `${dateColumn} > :${key}`,
949
+ params: { [key]: val },
950
+ };
951
+
952
+ case 'is_on_or_before':
953
+ return {
954
+ query: `${dateColumn} <= :${key}`,
955
+ params: { [key]: val },
956
+ };
957
+
958
+ case 'is_on_or_after':
959
+ return {
960
+ query: `${dateColumn} >= :${key}`,
961
+ params: { [key]: val },
962
+ };
963
+
964
+ // ============================================
965
+ // EMPTY / NOT EMPTY
966
+ // ============================================
967
+ case 'empty':
968
+ return {
969
+ query: `e.${attr} IS NULL`,
970
+ params: {},
971
+ };
972
+
973
+ case 'not_empty':
974
+ return {
975
+ query: `e.${attr} IS NOT NULL`,
976
+ params: {},
977
+ };
978
+
979
+ // ============================================
980
+ // DAY OFFSET LOGIC (ALWAYS ADJUST -1 DAY)
981
+ // ============================================
982
+ case 'is_day_before':
983
+ if (isNaN(numVal)) {
984
+ throw new BadRequestException(
985
+ 'Value must be a number for is_day_before',
986
+ );
987
+ }
988
+
989
+ const dayBefore = (() => {
990
+ const d = new Date();
991
+ d.setDate(d.getDate() - numVal);
992
+
993
+ // Format as YYYY-MM-DD in IST
994
+ return d.toLocaleDateString('en-CA', { timeZone: 'Asia/Kolkata' });
995
+ })();
996
+
997
+ return {
998
+ query: `${dateColumn} <= :${key}`,
999
+ params: { [key]: dayBefore },
1000
+ };
1001
+
1002
+ case 'is_day_after':
1003
+ if (isNaN(numVal)) {
1004
+ throw new BadRequestException(
1005
+ 'Value must be a number for is_day_after',
1006
+ );
1007
+ }
1008
+
1009
+ const dayAfter = (() => {
1010
+ const d = new Date();
1011
+ d.setDate(d.getDate() - 1 + numVal); // -1 because DB stores previous day
1012
+ return d.toISOString().split('T')[0];
1013
+ })();
1014
+
1015
+ return {
1016
+ query: `${dateColumn} > :${key}`,
1017
+ params: { [key]: dayAfter },
1018
+ };
1019
+
1020
+ // ============================================
1021
+ // MONTH OFFSET LOGIC
1022
+ // ============================================
1023
+ case 'is_month_before':
1024
+ if (isNaN(numVal)) {
1025
+ throw new BadRequestException(
1026
+ 'Value must be a number for is_month_before',
1027
+ );
1028
+ }
1029
+
1030
+ const monthBefore = (() => {
1031
+ const d = new Date();
1032
+ d.setMonth(d.getMonth() - numVal);
1033
+ d.setDate(1);
1034
+ d.setDate(d.getDate() - 1);
1035
+ return d.toISOString().split('T')[0];
1036
+ })();
1037
+
1038
+ return {
1039
+ query: `${monthColumn} < DATE_TRUNC('month', (:${key})::date)`,
1040
+ params: { [key]: monthBefore },
1041
+ };
1042
+
1043
+ case 'is_month_after':
1044
+ if (isNaN(numVal)) {
1045
+ throw new BadRequestException(
1046
+ 'Value must be a number for is_month_after',
1047
+ );
1048
+ }
1049
+
1050
+ const monthAfter = (() => {
1051
+ const d = new Date();
1052
+ d.setMonth(d.getMonth() + numVal);
1053
+ d.setDate(1);
1054
+ d.setDate(d.getDate() - 1);
1055
+ return d.toISOString().split('T')[0];
1056
+ })();
1057
+
1058
+ return {
1059
+ query: `${monthColumn} > DATE_TRUNC('month', (:${key})::date)`,
1060
+ params: { [key]: monthAfter },
1061
+ };
1062
+
1063
+ // ===== BETWEEN =====
1064
+ case 'between': {
1065
+ if (typeof val === 'string') {
1066
+ val = val.split(',').map((v) => v.trim());
1067
+ }
1068
+
1069
+ if (
1070
+ !Array.isArray(val) ||
1071
+ val.length !== 2 ||
1072
+ val[0] === '' ||
1073
+ val[1] === ''
1074
+ ) {
1075
+ console.log(`Invalid value for between: ${val}`);
1076
+ return null;
1077
+ }
1078
+
1079
+ return {
1080
+ query: `${dateColumn} BETWEEN :${key}_start AND :${key}_end`,
1081
+ params: {
1082
+ [`${key}_start`]: val[0],
1083
+ [`${key}_end`]: val[1],
1084
+ },
1085
+ };
1086
+ }
1087
+
1088
+ // ===== TODAY =====
1089
+ case 'today': {
1090
+ const today = moment().format('YYYY-MM-DD');
1091
+ return {
1092
+ query: `${dateColumn} = :today`,
1093
+ params: { today },
1094
+ };
1095
+ }
1096
+
1097
+ // ============================================
1098
+ // BUSINESS DAY OFFSET LOGIC (SKIPS WEEKENDS)
1099
+ // ALWAYS ADJUST -1 DAY FOR DB SHIFT
1100
+ // ============================================
1101
+
1102
+ case 'is_before_business_days': {
1103
+ if (isNaN(numVal)) {
1104
+ throw new BadRequestException('Value must be a number');
1105
+ }
1106
+
1107
+ const targetDate = subtractBusinessDays(numVal);
1108
+
1109
+ return {
1110
+ query: `${dateColumn} <= :${key} AND EXTRACT(DOW FROM ${dateColumn}) NOT IN (0, 6)`,
1111
+ params: { [key]: targetDate },
1112
+ };
1113
+ }
1114
+
1115
+ case 'is_after_business_days': {
1116
+ if (isNaN(numVal)) {
1117
+ throw new BadRequestException(
1118
+ 'Value must be a number for is_after_business_days',
1119
+ );
1120
+ }
1121
+
1122
+ const businessAfter = (() => {
1123
+ let d = new Date();
1124
+ let count = 0;
1125
+
1126
+ while (count < numVal) {
1127
+ d.setDate(d.getDate() + 1);
1128
+ const day = d.getDay(); // 0=Sun, 6=Sat
1129
+ if (day !== 0 && day !== 6) count++;
1130
+ }
1131
+
1132
+ // DB shift -1 day
1133
+ d.setDate(d.getDate() - 1);
1134
+
1135
+ return d.toISOString().split('T')[0];
1136
+ })();
1137
+
1138
+ return {
1139
+ query: `${dateColumn} > :${key}`,
1140
+ params: { [key]: businessAfter },
1141
+ };
1142
+ }
1143
+
1144
+ // ============================================
1145
+ // IN LAST DAY (range: today to N days before)
1146
+ // ============================================
1147
+ case 'in_last_day': {
1148
+ if (isNaN(numVal)) {
1149
+ throw new BadRequestException(
1150
+ 'Value must be a number for in_last_day',
1151
+ );
1152
+ }
1153
+
1154
+ const today = (() => {
1155
+ const d = new Date();
1156
+ return d.toISOString().split('T')[0];
1157
+ })();
1158
+
1159
+ const lastN = (() => {
1160
+ const d = new Date();
1161
+ d.setDate(d.getDate() - numVal);
1162
+ return d.toISOString().split('T')[0];
1163
+ })();
1164
+
1165
+ return {
1166
+ query: `${dateColumn} BETWEEN :${key}_start AND :${key}_end`,
1167
+ params: {
1168
+ [`${key}_start`]: lastN,
1169
+ [`${key}_end`]: today,
1170
+ },
1171
+ };
1172
+ }
1173
+
1174
+ // ============================================
1175
+ // IN NEXT DAY (range: today to N days after)
1176
+ // ============================================
1177
+ case 'in_next_day': {
1178
+ if (isNaN(numVal)) {
1179
+ throw new BadRequestException(
1180
+ 'Value must be a number for in_next_day',
1181
+ );
1182
+ }
1183
+
1184
+ const today = (() => {
1185
+ const d = new Date();
1186
+ return d.toISOString().split('T')[0];
1187
+ })();
1188
+
1189
+ const nextN = (() => {
1190
+ const d = new Date();
1191
+ d.setDate(d.getDate() + numVal);
1192
+ return d.toISOString().split('T')[0];
1193
+ })();
1194
+
1195
+ return {
1196
+ query: `${dateColumn} BETWEEN :${key}_start AND :${key}_end`,
1197
+ params: {
1198
+ [`${key}_start`]: today,
1199
+ [`${key}_end`]: nextN,
1200
+ },
1201
+ };
1202
+ }
1203
+
1204
+ default:
1205
+ throw new BadRequestException(`Unsupported operator for date: ${op}`);
1206
+ }
1207
+ }
1208
+
1209
+ private buildSelectCondition(
1210
+ attr: string,
1211
+ op: string,
1212
+ val: any,
1213
+ key: string,
1214
+ ) {
1215
+ switch (op) {
1216
+ case 'equal':
1217
+ return { query: `e.${attr} = :${key}`, params: { [key]: val } };
1218
+ case 'not_equal':
1219
+ return { query: `e.${attr} != :${key}`, params: { [key]: val } };
1220
+ case 'empty':
1221
+ return { query: `e.${attr} IS NULL`, params: {} };
1222
+ case 'not_empty':
1223
+ return { query: `e.${attr} IS NOT NULL`, params: {} };
1224
+ default:
1225
+ throw new BadRequestException(`Unsupported operator for select: ${op}`);
1226
+ }
1227
+ }
1228
+
1229
+ private buildMultiSelectCondition(
1230
+ attr: string,
1231
+ op: string,
1232
+ val: any,
1233
+ key: string,
1234
+ ) {
1235
+ let arr: string[] = [];
1236
+
1237
+ // SAFETY: Convert all to array
1238
+ if (Array.isArray(val)) {
1239
+ arr = val.map((v) => String(v));
1240
+ } else if (typeof val === 'string') {
1241
+ arr = val.split(',').map((v) => v.trim());
1242
+ } else {
1243
+ throw new BadRequestException(`Invalid multiselect value`);
1244
+ }
1245
+
1246
+ if (arr.length === 0) {
1247
+ return { query: '1=1', params: {} };
1248
+ }
1249
+
1250
+ if (op === 'equal') {
1251
+ return {
1252
+ query: `e.${attr}::text IN (:...${key})`,
1253
+ params: { [key]: arr },
1254
+ };
1255
+ }
1256
+
1257
+ if (op === 'not_equal') {
1258
+ return {
1259
+ query: `e.${attr}::text NOT IN (:...${key})`,
1260
+ params: { [key]: arr },
1261
+ };
1262
+ }
1263
+
1264
+ if (op === 'contains') {
1265
+ return {
1266
+ query: `e.${attr}::text LIKE :${key}`,
1267
+ params: { [key]: `%${val}%` },
1268
+ };
1269
+ }
1270
+
1271
+ if (op === 'not_contains') {
1272
+ return {
1273
+ query: `e.${attr}::text NOT LIKE :${key}`,
1274
+ params: { [key]: `%${val}%` },
1275
+ };
1276
+ }
1277
+
1278
+ if (op === 'empty') return { query: `e.${attr} IS NULL`, params: {} };
1279
+ if (op === 'not_empty')
1280
+ return { query: `e.${attr} IS NOT NULL`, params: {} };
1281
+
1282
+ throw new BadRequestException(`Unsupported operator: ${op}`);
1283
+ }
1284
+
1285
+ private buildYearCondition(attr: string, op: string, val: any, key: string) {
1286
+ switch (op) {
1287
+ case 'equal':
1288
+ return {
1289
+ query: `e.${attr} = :${key}`,
1290
+ params: { [key]: parseInt(val, 10) },
1291
+ };
1292
+ case 'not_equal':
1293
+ return {
1294
+ query: `e.${attr} != :${key}`,
1295
+ params: { [key]: parseInt(val, 10) },
1296
+ };
1297
+ case 'greater_than':
1298
+ return {
1299
+ query: `e.${attr} > :${key}`,
1300
+ params: { [key]: parseInt(val, 10) },
1301
+ };
1302
+ case 'less_than':
1303
+ return {
1304
+ query: `e.${attr} < :${key}`,
1305
+ params: { [key]: parseInt(val, 10) },
1306
+ };
1307
+ default:
1308
+ throw new BadRequestException(`Unsupported operator for year: ${op}`);
1309
+ }
1310
+ }
1311
+
1312
+ async queryWithSchema(sql: string, params: any[] = []) {
1313
+ await this.entityManager.query('BEGIN');
1314
+ await this.entityManager.query(`SET LOCAL search_path TO ${this.schema}`);
1315
+ const result = await this.entityManager.query(sql, params);
1316
+ await this.entityManager.query('COMMIT');
1317
+ return result;
1318
+ }
1319
+ }