@restforgejs/platform 4.2.8 → 4.3.1

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 (324) hide show
  1. package/SECURITY.md +83 -4
  2. package/bin/sdf-tools.exe +0 -0
  3. package/build-info.json +2 -2
  4. package/cli/consumer-deploy.js +1 -1
  5. package/cli/consumer.js +1 -1
  6. package/generators/cli/dashboard/create.js +4 -1
  7. package/generators/cli/endpoint/create.js +1 -1
  8. package/generators/cli/key/generate.js +2 -1
  9. package/generators/cli/key/revoke.js +2 -1
  10. package/generators/cli/payload/diff.js +3 -2
  11. package/generators/cli/payload/generate.js +3 -2
  12. package/generators/cli/payload/sync.js +3 -2
  13. package/generators/cli/payload/validate.js +3 -2
  14. package/generators/cli/processor/create.js +14 -3
  15. package/generators/cli/project/delete.js +2 -1
  16. package/generators/cli/query/validate.js +3 -2
  17. package/generators/cli/schema/apply.js +3 -2
  18. package/generators/cli/schema/describe.js +3 -2
  19. package/generators/cli/schema/diff.js +3 -2
  20. package/generators/cli/schema/introspect.js +3 -2
  21. package/generators/cli/schema/list.js +3 -2
  22. package/generators/cli/schema/migrate.js +3 -2
  23. package/generators/lib/migration/audit-table-runner.js +213 -215
  24. package/generators/lib/templates/dashboard-catalog.js +1 -437
  25. package/generators/lib/templates/db-connection-env.js +1 -212
  26. package/generators/lib/templates/dbschema-catalog.js +1 -489
  27. package/generators/lib/templates/field-validation-catalog.js +1 -531
  28. package/generators/lib/templates/mysql-template.js +1 -3863
  29. package/generators/lib/templates/oracle-template.js +1 -3915
  30. package/generators/lib/templates/postgres-template.js +1 -5838
  31. package/generators/lib/templates/query-declarative-catalog.js +1 -199
  32. package/generators/lib/templates/sqlite-template.js +1 -3440
  33. package/generators/lib/utils/env-manager.js +6 -0
  34. package/generators/lib/utils/path-validator.js +71 -0
  35. package/generators/lib/validators/payload-validator.js +1 -2
  36. package/integrity-manifest.json +28 -10
  37. package/package.json +11 -3
  38. package/scripts/verify-integrity.js +1 -1
  39. package/server.js +1 -1
  40. package/src/components/handlers/adjust_handler.js +1 -1
  41. package/src/components/handlers/audit_handler.js +1 -1
  42. package/src/components/handlers/delete_handler.js +1 -1
  43. package/src/components/handlers/export_handler.js +1 -1
  44. package/src/components/handlers/import_handler.js +1 -1
  45. package/src/components/handlers/insert_handler.js +1 -1
  46. package/src/components/handlers/update_handler.js +1 -1
  47. package/src/components/handlers/upload_handler.js +1 -1
  48. package/src/components/handlers/workflow_handler.js +1 -1
  49. package/src/components/integrations/webhook.js +1 -1
  50. package/src/consumers/baseConsumer.js +1 -1
  51. package/src/consumers/declarativeMapper.js +1 -1
  52. package/src/consumers/handlers/apiHandler.js +1 -1
  53. package/src/consumers/handlers/consoleHandler.js +1 -1
  54. package/src/consumers/handlers/databaseHandler.js +1 -1
  55. package/src/consumers/handlers/index.js +1 -1
  56. package/src/consumers/handlers/kafkaHandler.js +1 -1
  57. package/src/consumers/index.js +1 -1
  58. package/src/consumers/messageTransformer.js +1 -1
  59. package/src/consumers/validator.js +1 -1
  60. package/src/core/db/dialect/base-dialect.js +1 -1
  61. package/src/core/db/dialect/index.js +1 -1
  62. package/src/core/db/dialect/mysql-dialect.js +1 -1
  63. package/src/core/db/dialect/oracle-dialect.js +1 -1
  64. package/src/core/db/dialect/postgres-dialect.js +1 -1
  65. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  66. package/src/core/db/flatten-helper.js +1 -1
  67. package/src/core/db/query-builder-error.js +1 -1
  68. package/src/core/db/query-builder.js +1 -1
  69. package/src/core/db/relation-helper.js +1 -1
  70. package/src/core/handlers/delete_handler.js +1 -1
  71. package/src/core/handlers/insert_handler.js +1 -1
  72. package/src/core/handlers/update_handler.js +1 -1
  73. package/src/core/models/base-model.js +1 -1
  74. package/src/core/utils/cache-manager.js +1 -1
  75. package/src/core/utils/component-engine.js +1 -1
  76. package/src/core/utils/context-builder.js +1 -1
  77. package/src/core/utils/datetime-formatter.js +1 -1
  78. package/src/core/utils/datetime-parser.js +1 -1
  79. package/src/core/utils/db.js +1 -1
  80. package/src/core/utils/logger.js +1 -1
  81. package/src/core/utils/payload-loader.js +1 -1
  82. package/src/core/utils/security-checks.js +1 -1
  83. package/src/middleware/body-options.js +1 -1
  84. package/src/middleware/cors.js +1 -1
  85. package/src/middleware/idempotency.js +1 -1
  86. package/src/middleware/rate-limiter.js +1 -1
  87. package/src/middleware/request-logger.js +1 -1
  88. package/src/middleware/security-headers.js +1 -1
  89. package/src/models/base-model-mysql.js +1 -1
  90. package/src/models/base-model-oracle.js +1 -1
  91. package/src/models/base-model-sqlite.js +1 -1
  92. package/src/models/base-model.js +1 -1
  93. package/src/pro/caching/redis-client.js +1 -1
  94. package/src/pro/caching/redis-helper.js +1 -1
  95. package/src/pro/consumers/baseConsumer.js +1 -1
  96. package/src/pro/consumers/declarativeMapper.js +1 -1
  97. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  98. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  99. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  100. package/src/pro/consumers/handlers/index.js +1 -1
  101. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  102. package/src/pro/consumers/index.js +1 -1
  103. package/src/pro/consumers/messageTransformer.js +1 -1
  104. package/src/pro/consumers/validator.js +1 -1
  105. package/src/pro/database/base-model-mysql.js +1 -1
  106. package/src/pro/database/base-model-oracle.js +1 -1
  107. package/src/pro/database/base-model-sqlite.js +1 -1
  108. package/src/pro/database/db-mysql.js +1 -1
  109. package/src/pro/database/db-oracle.js +1 -1
  110. package/src/pro/database/db-sqlite.js +1 -1
  111. package/src/pro/excel/excel-generator.js +1 -1
  112. package/src/pro/excel/excel-parser.js +1 -1
  113. package/src/pro/excel/export-service.js +1 -1
  114. package/src/pro/excel/export_handler.js +1 -1
  115. package/src/pro/excel/import-service.js +1 -1
  116. package/src/pro/excel/import-validator.js +1 -1
  117. package/src/pro/excel/import_handler.js +1 -1
  118. package/src/pro/excel/upsert-builder.js +1 -1
  119. package/src/pro/idgen/idgen-routes.js +1 -1
  120. package/src/pro/integrations/lookup-resolver.js +1 -1
  121. package/src/pro/integrations/upload-handler-v2.js +1 -1
  122. package/src/pro/integrations/upload-handler.js +1 -1
  123. package/src/pro/integrations/webhook.js +1 -1
  124. package/src/pro/locking/lock-routes.js +1 -1
  125. package/src/pro/locking/resource-lock-manager.js +1 -1
  126. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  127. package/src/pro/messaging/kafkaService.js +1 -1
  128. package/src/pro/messaging/messagehubService.js +1 -1
  129. package/src/pro/messaging/rabbitmqService.js +1 -1
  130. package/src/pro/scheduler/job-manager.js +1 -1
  131. package/src/pro/scheduler/job-routes.js +1 -1
  132. package/src/pro/scheduler/job-validator.js +1 -1
  133. package/src/pro/storage/base-storage-provider.js +1 -1
  134. package/src/pro/storage/file-metadata-helper.js +1 -1
  135. package/src/pro/storage/index.js +1 -1
  136. package/src/pro/storage/local-storage-provider.js +1 -1
  137. package/src/pro/storage/s3-storage-provider.js +1 -1
  138. package/src/pro/storage/upload-cleanup-job.js +1 -1
  139. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  140. package/src/pro/storage/upload-pending-tracker.js +1 -1
  141. package/src/pro/websocket/broadcast-helper.js +1 -1
  142. package/src/pro/websocket/index.js +1 -1
  143. package/src/pro/websocket/livesync-server.js +1 -1
  144. package/src/pro/websocket/ws-broadcaster.js +1 -1
  145. package/src/services/export-service.js +1 -1
  146. package/src/services/import-service.js +1 -1
  147. package/src/services/kafkaConsumerService.js +1 -1
  148. package/src/services/kafkaService.js +1 -1
  149. package/src/services/messagehubService.js +1 -1
  150. package/src/services/rabbitmqService.js +1 -1
  151. package/src/utils/cache-invalidation-registry.js +1 -1
  152. package/src/utils/cache-manager.js +1 -1
  153. package/src/utils/component-engine.js +1 -1
  154. package/src/utils/config-extractor.js +1 -1
  155. package/src/utils/consumerLogger.js +1 -1
  156. package/src/utils/context-builder.js +1 -1
  157. package/src/utils/dashboard-helpers.js +1 -1
  158. package/src/utils/dateHelper.js +1 -1
  159. package/src/utils/datetime-formatter.js +1 -1
  160. package/src/utils/datetime-parser.js +1 -1
  161. package/src/utils/db-bootstrap.js +1 -1
  162. package/src/utils/db-mysql.js +1 -1
  163. package/src/utils/db-oracle.js +1 -1
  164. package/src/utils/db-sqlite.js +1 -1
  165. package/src/utils/db.js +1 -1
  166. package/src/utils/demo-generator.js +1 -1
  167. package/src/utils/excel-generator.js +1 -1
  168. package/src/utils/excel-parser.js +1 -1
  169. package/src/utils/file-watcher.js +1 -1
  170. package/src/utils/id-generator.js +1 -1
  171. package/src/utils/idempotency-manager.js +1 -1
  172. package/src/utils/import-validator.js +1 -1
  173. package/src/utils/license-client.js +1 -1
  174. package/src/utils/lock-manager.js +1 -1
  175. package/src/utils/logger.js +1 -1
  176. package/src/utils/lookup-resolver.js +1 -1
  177. package/src/utils/payload-loader.js +1 -1
  178. package/src/utils/processor-response.js +1 -1
  179. package/src/utils/rabbitmq.js +1 -1
  180. package/src/utils/redis-client.js +1 -1
  181. package/src/utils/redis-helper.js +1 -1
  182. package/src/utils/request-scope.js +1 -1
  183. package/src/utils/security-checks.js +1 -1
  184. package/src/utils/service-resolver.js +1 -1
  185. package/src/utils/shutdown-coordinator.js +1 -1
  186. package/src/utils/trusted-keys.js +1 -1
  187. package/src/utils/upload-handler.js +1 -1
  188. package/src/utils/upsert-builder.js +1 -1
  189. package/src/utils/workflow-hook-executor.js +1 -1
  190. package/generators/metadata/global.json +0 -58
  191. package/generators/metadata/test-mysql-workbench.json +0 -118
  192. package/generators/metadata/test-mysql.json +0 -56
  193. package/generators/metadata/test-oracle-workbench.json +0 -118
  194. package/generators/metadata/test-oracle.json +0 -56
  195. package/generators/metadata/test-pg-workbench.json +0 -118
  196. package/generators/metadata/test-pg.json +0 -56
  197. package/generators/scripts/obfuscate-source.js +0 -356
  198. package/generators/scripts/validate-catalog.js +0 -430
  199. package/generators/scripts/validate-dbschema-catalog.js +0 -708
  200. package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js +0 -944
  201. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  202. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  203. package/generators/tests/baseline/oracle/mini_inventory_item/src/models/mini-inventory/item.js +0 -1002
  204. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  205. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  206. package/generators/tests/baseline/postgres/mini_inventory_item/src/models/mini-inventory/item.js +0 -1333
  207. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory/item.js +0 -1173
  208. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory.js +0 -496
  209. package/generators/tests/fixtures/payloads/custom-sensitive.json +0 -27
  210. package/generators/tests/fixtures/payloads/dynamic-search-optout.json +0 -23
  211. package/generators/tests/fixtures/payloads/login-with-password.json +0 -22
  212. package/generators/tests/fixtures/payloads/order-process.json +0 -52
  213. package/generators/tests/fixtures/payloads/with-inline-sql.json +0 -26
  214. package/generators/tests/integration-tahap4b/README.md +0 -145
  215. package/generators/tests/integration-tahap4b/run-concurrent.js +0 -77
  216. package/generators/tests/integration-tahap4b/seed.sql +0 -53
  217. package/generators/tests/integration-tahap4b/verify.sql +0 -110
  218. package/generators/tests/unit/cli/create-dashboard.test.js +0 -505
  219. package/generators/tests/unit/cli/create-processor.test.js +0 -319
  220. package/generators/tests/unit/cli/dispatch-dashboard.test.js +0 -149
  221. package/generators/tests/unit/lib/dashboard-generator.test.js +0 -895
  222. package/generators/tests/unit/lib/dashboard-validator.test.js +0 -354
  223. package/generators/tests/unit/lib/dbschema-kit/apply-executor.test.js +0 -437
  224. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-introspect.test.js +0 -393
  225. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-generate-ddl.test.js +0 -104
  226. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-init.test.js +0 -119
  227. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-list.test.js +0 -48
  228. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-migrate.test.js +0 -175
  229. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-validate.test.js +0 -102
  230. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-models.test.js +0 -43
  231. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/all-schemas-listing.js +0 -84
  232. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/connection-error.js +0 -13
  233. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/empty.js +0 -12
  234. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/multi-schema.js +0 -124
  235. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/single-schema-inventory.js +0 -64
  236. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/two-tables.js +0 -66
  237. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/connection-error.js +0 -9
  238. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/partial.js +0 -29
  239. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/rollback.js +0 -26
  240. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/success.js +0 -43
  241. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/audit/events.js +0 -18
  242. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/inventory/products.js +0 -9
  243. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/users.js +0 -8
  244. package/generators/tests/unit/lib/dbschema-kit/connection.test.js +0 -112
  245. package/generators/tests/unit/lib/dbschema-kit/ddl-generator.test.js +0 -205
  246. package/generators/tests/unit/lib/dbschema-kit/define-model.test.js +0 -56
  247. package/generators/tests/unit/lib/dbschema-kit/dialect/index.test.js +0 -46
  248. package/generators/tests/unit/lib/dbschema-kit/dialect/mysql.test.js +0 -126
  249. package/generators/tests/unit/lib/dbschema-kit/dialect/oracle.test.js +0 -126
  250. package/generators/tests/unit/lib/dbschema-kit/dialect/postgres.test.js +0 -131
  251. package/generators/tests/unit/lib/dbschema-kit/dialect/sqlite.test.js +0 -126
  252. package/generators/tests/unit/lib/dbschema-kit/driver-loader.test.js +0 -93
  253. package/generators/tests/unit/lib/dbschema-kit/emitters/create-index.test.js +0 -173
  254. package/generators/tests/unit/lib/dbschema-kit/emitters/create-table.test.js +0 -376
  255. package/generators/tests/unit/lib/dbschema-kit/emitters/drop-table.test.js +0 -78
  256. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/invalid-dialect.env +0 -6
  257. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-dialect.env +0 -5
  258. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-host.env +0 -5
  259. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/oracle-valid.env +0 -6
  260. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/postgres-valid.env +0 -7
  261. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/sqlite-valid.env +0 -2
  262. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/category.js +0 -11
  263. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/item_product.js +0 -11
  264. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound.js +0 -24
  265. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound_item.js +0 -28
  266. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/supplier.js +0 -9
  267. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/warehouse.js +0 -9
  268. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-invalid/orphan.js +0 -17
  269. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/category.js +0 -11
  270. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/item_product.js +0 -11
  271. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/supplier.js +0 -9
  272. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/warehouse.js +0 -9
  273. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound.js +0 -24
  274. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound_item.js +0 -28
  275. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/audit/events.js +0 -18
  276. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/inventory/products.js +0 -9
  277. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/public/users.js +0 -9
  278. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/extra/category.js +0 -8
  279. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/master/category.js +0 -8
  280. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/bar.js +0 -8
  281. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/foo.js +0 -8
  282. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/empty-folder/README.md +0 -1
  283. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-export/plain.js +0 -3
  284. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-schema/bad.js +0 -6
  285. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/legacy-pattern/legacy.js +0 -12
  286. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/audit/products.js +0 -9
  287. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/inventory/products.js +0 -9
  288. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/a/products.js +0 -8
  289. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/b/products.js +0 -8
  290. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/nested-deep/a/b/c/deep_table.js +0 -8
  291. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/.hidden/ignored.js +0 -7
  292. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/category.js +0 -8
  293. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/supplier.js +0 -8
  294. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound.js +0 -8
  295. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound_item.js +0 -8
  296. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/category.js +0 -8
  297. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/item_product.js +0 -9
  298. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-single/category.js +0 -8
  299. package/generators/tests/unit/lib/dbschema-kit/integration.test.js +0 -217
  300. package/generators/tests/unit/lib/dbschema-kit/introspect-mapper.test.js +0 -403
  301. package/generators/tests/unit/lib/dbschema-kit/ir-builder.test.js +0 -390
  302. package/generators/tests/unit/lib/dbschema-kit/loader.test.js +0 -128
  303. package/generators/tests/unit/lib/dbschema-kit/naming.test.js +0 -170
  304. package/generators/tests/unit/lib/dbschema-kit/parser/shorthand-parser.test.js +0 -237
  305. package/generators/tests/unit/lib/dbschema-kit/schema-printer.test.js +0 -251
  306. package/generators/tests/unit/lib/dbschema-kit/statement-modifier.test.js +0 -105
  307. package/generators/tests/unit/lib/dbschema-kit/statement-splitter.test.js +0 -165
  308. package/generators/tests/unit/lib/dbschema-kit/topological-sort.test.js +0 -135
  309. package/generators/tests/unit/lib/dbschema-kit/validator/check-compatibility-validator.test.js +0 -373
  310. package/generators/tests/unit/lib/dbschema-kit/validator/circular-relation-validator.test.js +0 -454
  311. package/generators/tests/unit/lib/dbschema-kit/validator/cross-model-validator.test.js +0 -512
  312. package/generators/tests/unit/lib/dbschema-kit/validator/enhanced-validate-integration.test.js +0 -390
  313. package/generators/tests/unit/lib/dbschema-kit/validator/naming-convention-validator.test.js +0 -306
  314. package/generators/tests/unit/lib/dbschema-kit/validator/schema-validator.test.js +0 -443
  315. package/generators/tests/unit/lib/dbschema-kit/validator/type-compatibility-validator.test.js +0 -440
  316. package/generators/tests/unit/lib/dbschema-kit/validator/validator-reporter.test.js +0 -172
  317. package/generators/tests/unit/lib/metadata-manager-dashboard.test.js +0 -256
  318. package/generators/tests/unit/lib/payload-validator-fieldpolicy.test.js +0 -240
  319. package/generators/tests/unit/lib/processor-validation-generator.test.js +0 -300
  320. package/generators/tests/unit/lib/sensitive-field-masker.test.js +0 -170
  321. package/generators/tests/unit/lib/sql-table-extractor.test.js +0 -119
  322. package/scripts/generate-integrity-manifest.js +0 -124
  323. package/scripts/snapshot-cli-contracts.js +0 -194
  324. package/scripts/verify-publish.js +0 -56
@@ -1,3915 +1 @@
1
- /**
2
- * Oracle Database Templates
3
- * Template generator untuk Oracle database
4
- * Paritas fungsional dengan PostgreSQL template
5
- */
6
-
7
- /**
8
- * Konversi string kebab-case/snake_case ke camelCase
9
- */
10
- function toCamelCase(str) {
11
- if (!str || typeof str !== 'string') return '';
12
- return str
13
- .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
14
- return index === 0 ? word.toLowerCase() : word.toUpperCase();
15
- })
16
- .replace(/\s+/g, '')
17
- .replace(/[-_]/g, '');
18
- }
19
-
20
- /**
21
- * Konversi string kebab-case/snake_case ke PascalCase
22
- */
23
- function toPascalCase(str) {
24
- if (!str || typeof str !== 'string') return '';
25
- const camelCase = toCamelCase(str);
26
- return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
27
- }
28
-
29
- /**
30
- * Deteksi kolom text utama untuk lookup dari daftar field names
31
- */
32
- function detectTextColumn(fieldNames) {
33
- const priorities = ['name', 'nama', 'title', 'judul', 'description', 'deskripsi', 'code', 'kode'];
34
- for (const priority of priorities) {
35
- const found = fieldNames.find(f => f.toLowerCase().includes(priority));
36
- if (found) return found;
37
- }
38
- return fieldNames.find(field => field !== 'id') || fieldNames[0] || 'nama';
39
- }
40
-
41
- /**
42
- * Build audit columns section untuk constructor model.
43
- * Membaca payload.auditColumns dan emit baris this.auditColumns yang sesuai.
44
- */
45
- function buildAuditColumnsSection(payload, indent) {
46
- if (!('auditColumns' in payload)) return '';
47
- const value = payload.auditColumns;
48
-
49
- if (value === false || value === null) {
50
- return `\n${indent}// Tabel tanpa audit columns: disable injection helper\n${indent}this.auditColumns = null;\n`;
51
- }
52
-
53
- if (typeof value === 'object' && !Array.isArray(value)) {
54
- const validKeys = ['createdAt', 'createdBy', 'updatedAt', 'updatedBy'];
55
- const filtered = {};
56
- validKeys.forEach(k => {
57
- if (value[k] !== undefined) filtered[k] = value[k];
58
- });
59
- const json = JSON.stringify(filtered, null, 2)
60
- .split('\n')
61
- .map((l, i) => i === 0 ? l : indent + l)
62
- .join('\n');
63
- return `\n${indent}// Custom audit columns mapping dari payload\n${indent}this.auditColumns = ${json};\n`;
64
- }
65
-
66
- throw new Error(`Invalid auditColumns value for ${payload.tableName}: must be false, null, or object`);
67
- }
68
-
69
- /**
70
- * Membuat template untuk main module Oracle
71
- */
72
- function createOracleMainModuleTemplate(moduleName) {
73
- const moduleNameCapitalized = toPascalCase(moduleName);
74
- return `const express = require('express');
75
- const bodyParser = require('body-parser');
76
- const path = require('path');
77
- const fs = require('fs');
78
- const { v4: uuidv4 } = require('uuid');
79
- const { logger, logServerReady, logEndpointRegistered, createRequestLogger, logRequest } = require('@restforgejs/platform/src/utils/logger');
80
- const ExportHandler = require('@restforgejs/platform/src/components/handlers/export_handler');
81
- const ImportHandler = require('@restforgejs/platform/src/components/handlers/import_handler');
82
- const UploadHandler = require('@restforgejs/platform/src/components/handlers/upload_handler');
83
- const { extractExportConfigFromEndpoint, extractImportConfigFromEndpoint, extractUploadConfigFromEndpoint } = require('@restforgejs/platform/src/utils/config-extractor');
84
- const rateLimiter = require('@restforgejs/platform/src/middleware/rate-limiter');
85
- const idempotencyMiddleware = require('@restforgejs/platform/src/middleware/idempotency');
86
- const bodyOptionsMiddleware = require('@restforgejs/platform/src/middleware/body-options');
87
- const corsMiddleware = require('@restforgejs/platform/src/middleware/cors');
88
- const securityHeaders = require('@restforgejs/platform/src/middleware/security-headers');
89
-
90
- /**
91
- * Fungsi untuk mengeksekusi modul ${moduleName} (Oracle Database)
92
- * @param {Object} config - Konfigurasi untuk menjalankan modul
93
- * @param {number} config.port - Port untuk server
94
- * @param {string} config.key - API Key (opsional)
95
- * @returns {Promise<void>} Promise yang tidak pernah resolve agar server tetap berjalan
96
- */
97
- async function execute(config) {
98
- return new Promise((resolve) => {
99
- const app = express();
100
- const port = config.port || 3000;
101
- const serverAddress = config.serverAddress || '0.0.0.0';
102
- const moduleNameCapitalized = '${moduleNameCapitalized}';
103
-
104
- // Configuration options
105
- const loggingEnabled = config.logging !== false;
106
- const apiKeyRequired = !!config.key;
107
-
108
- logger.info({
109
- event: 'module_starting',
110
- module: moduleNameCapitalized,
111
- port,
112
- cors: process.env.CORS_ENABLED !== 'false',
113
- helmet: process.env.HELMET_ENABLED === 'true',
114
- logging: loggingEnabled,
115
- apiKey: apiKeyRequired
116
- }, \`Starting \${moduleNameCapitalized} module\`);
117
-
118
- // CORS middleware (konfigurasi via CORS_ENABLED dan CORS_ORIGINS di .env)
119
- app.use(corsMiddleware.middleware());
120
-
121
- // Security headers middleware (konfigurasi via HELMET_ENABLED di .env)
122
- app.use(securityHeaders.middleware());
123
-
124
- // Middleware untuk parsing JSON dengan penanganan error
125
- app.use(bodyParser.json({
126
- verify: (req, res, buf, encoding) => {
127
- if (buf.length === 0) {
128
- return;
129
- }
130
- try {
131
- JSON.parse(buf);
132
- } catch (error) {
133
- res.status(400).json({
134
- success: false,
135
- error: 'Invalid JSON payload',
136
- message: 'The payload sent is not a valid JSON format',
137
- details: error.message
138
- });
139
- throw new Error('Invalid JSON');
140
- }
141
- }
142
- }));
143
-
144
- app.use(bodyParser.urlencoded({ extended: true }));
145
-
146
- // Request logging middleware (Pino-based)
147
- app.use((req, res, next) => {
148
- req.id = req.headers['x-request-id'] || uuidv4();
149
- res.set('X-Request-ID', req.id);
150
-
151
- req.log = createRequestLogger({
152
- requestId: req.id,
153
- method: req.method,
154
- path: req.path,
155
- ip: req.ip
156
- });
157
-
158
- const startTime = process.hrtime();
159
-
160
- res.on('finish', () => {
161
- const [seconds, nanoseconds] = process.hrtime(startTime);
162
- const durationMs = parseFloat((seconds * 1000 + nanoseconds / 1e6).toFixed(2));
163
- logRequest(req, res, durationMs);
164
- });
165
-
166
- next();
167
- });
168
-
169
- // Middleware untuk validasi API key jika diperlukan
170
- if (config.key) {
171
- app.use((req, res, next) => {
172
- const apiKey = req.headers['x-api-key'];
173
- if (!apiKey || apiKey !== config.key) {
174
- return res.status(401).json({ error: 'Unauthorized: Invalid API Key' });
175
- }
176
- next();
177
- });
178
- }
179
-
180
- // Rate limiting middleware (store: memory untuk single mode, Redis untuk cluster mode)
181
- rateLimiter.setStore(config.cluster ? 'redis' : 'memory');
182
- app.use('/api', rateLimiter.middleware());
183
-
184
- // Idempotency middleware (protects mutation endpoints from duplicate execution)
185
- if (process.env.IDEMPOTENCY_ENABLED === 'true') {
186
- app.use('/api', idempotencyMiddleware.middleware());
187
- }
188
-
189
- // Body options middleware (extract {data, options} format dari request body)
190
- app.use('/api', bodyOptionsMiddleware.middleware());
191
-
192
- // Auto-load plugin (jika ada)
193
- const moduleName = '${moduleName}';
194
- const pluginPath = path.join(__dirname, '..', 'plugins', \`\${moduleName}-plugin.js\`);
195
- let plugin = null;
196
- if (fs.existsSync(pluginPath)) {
197
- try {
198
- plugin = require(pluginPath);
199
- if (plugin.onBeforeEndpointsLoad) {
200
- plugin.onBeforeEndpointsLoad(app, config);
201
- }
202
- logger.info({ event: 'plugin_loaded', plugin: \`\${moduleName}-plugin\` }, \`Plugin loaded: \${moduleName}-plugin.js\`);
203
- } catch (pluginError) {
204
- logger.error({ event: 'plugin_load_error', error: pluginError.message }, \`Failed to load plugin: \${moduleName}-plugin.js\`);
205
- }
206
- }
207
-
208
- // Health check endpoint
209
- app.get('/api/${moduleName}/health', (req, res) => {
210
- const healthInfo = {
211
- status: 'ok',
212
- timestamp: new Date().toISOString().replace('T', ' ').replace(/\\.\\d{3}Z$/, ''),
213
- service: '${moduleName}',
214
- uptime: process.uptime(),
215
- memory: process.memoryUsage(),
216
- system: {
217
- platform: process.platform,
218
- nodeVersion: process.version,
219
- pid: process.pid
220
- }
221
- };
222
-
223
- res.json(healthInfo);
224
- });
225
-
226
- // Muat semua rute dari folder ${moduleName}
227
- const modulesDir = path.join(__dirname, '${moduleName}');
228
-
229
- try {
230
- if (!fs.existsSync(modulesDir)) {
231
- fs.mkdirSync(modulesDir, { recursive: true });
232
- console.log(\`Directory \$\{modulesDir} created successfully\`);
233
- }
234
-
235
- const files = fs.readdirSync(modulesDir);
236
- const endpointFiles = files.filter(file => file.endsWith('.js'));
237
-
238
- if (endpointFiles.length === 0) {
239
- console.log(\`No endpoint files found in \${modulesDir}\`);
240
- console.log(\`Add endpoint files to enable API functionality\`);
241
- } else {
242
- logger.info({ event: 'endpoints_loading', count: endpointFiles.length }, \`Loading \${endpointFiles.length} endpoint(s)\`);
243
- }
244
-
245
- for (const file of endpointFiles) {
246
- try {
247
- const endpointName = path.basename(file, '.js');
248
- const endpointPath = path.join(modulesDir, file);
249
-
250
- // Clear module cache untuk development
251
- if (require.cache[endpointPath]) {
252
- delete require.cache[endpointPath];
253
- }
254
-
255
- const moduleRoutes = require(endpointPath);
256
-
257
- const endpointPrefix = \`/api/${moduleName}/\$\{endpointName}\`;
258
- app.use(endpointPrefix, moduleRoutes);
259
- logEndpointRegistered(endpointName, endpointPrefix);
260
-
261
- // Register export routes via centralized handler
262
- try {
263
- const exportConfig = extractExportConfigFromEndpoint(endpointPath);
264
- if (exportConfig) {
265
- ExportHandler.registerRoutes(app, '${moduleName}', endpointName, exportConfig);
266
- }
267
- } catch (exportError) {
268
- logger.error({ event: 'export_registration_error', endpoint: endpointName, error: exportError.message }, \`Export registration failed for \$\{endpointName}: \$\{exportError.message}\`);
269
- }
270
-
271
- // Register import routes via centralized handler
272
- try {
273
- const importConfig = extractImportConfigFromEndpoint(endpointPath);
274
- if (importConfig) {
275
- ImportHandler.registerRoutes(app, '${moduleName}', endpointName, importConfig);
276
- logger.info({ event: 'import_routes_registered', endpoint: endpointName }, \`Import routes registered for \$\{endpointName}\`);
277
- }
278
- } catch (importError) {
279
- logger.error({ event: 'import_registration_error', endpoint: endpointName, error: importError.message }, \`Import registration failed for \$\{endpointName}: \$\{importError.message}\`);
280
- }
281
-
282
- // Register upload routes via centralized handler
283
- try {
284
- const uploadConfig = extractUploadConfigFromEndpoint(endpointPath);
285
- if (uploadConfig) {
286
- UploadHandler.registerRoutes(app, '${moduleName}', endpointName, uploadConfig);
287
- logger.info({ event: 'upload_routes_registered', endpoint: endpointName, fields: Object.keys(uploadConfig.fields || {}) }, \`Upload routes registered for \$\{endpointName}\`);
288
- }
289
- } catch (uploadError) {
290
- logger.error({ event: 'upload_registration_error', endpoint: endpointName, error: uploadError.message }, \`Upload registration failed for \$\{endpointName}: \$\{uploadError.message}\`);
291
- }
292
- } catch (error) {
293
- console.error(\`Error loading module \$\{file} from ${moduleName}:\`, error);
294
- throw error;
295
- }
296
- }
297
-
298
- // Register export cleanup route
299
- try {
300
- ExportHandler.registerCleanupRoute(app);
301
- } catch (cleanupError) {
302
- console.error('Export cleanup route registration failed:', cleanupError.message);
303
- }
304
-
305
- app.get('/', (req, res) => {
306
- res.json({
307
- message: '${moduleName} API (Oracle Database)',
308
- status: 'running',
309
- database: 'Oracle'
310
- });
311
- });
312
-
313
- // Error handling middleware
314
- app.use((err, req, res, next) => {
315
- console.error('Error:', err);
316
-
317
- if (err instanceof SyntaxError && err.status === 400 && 'body' in err && !res.headersSent) {
318
- return res.status(400).json({
319
- success: false,
320
- error: 'Invalid JSON payload',
321
- message: 'The payload sent is not a valid JSON format',
322
- details: err.message
323
- });
324
- }
325
-
326
- if (!res.headersSent) {
327
- return res.status(500).json({
328
- success: false,
329
- error: 'Internal Server Error',
330
- message: 'An error occurred on the server',
331
- details: err.message
332
- });
333
- }
334
-
335
- next(err);
336
- });
337
-
338
- // Hook untuk mount middleware tambahan sebelum catch-all 403
339
- // Digunakan oleh runtime untuk register admin endpoint dan middleware eksternal lainnya
340
- if (config.onAppReady && typeof config.onAppReady === 'function') {
341
- config.onAppReady(app);
342
- }
343
-
344
- app.use((req, res) => {
345
- res.status(403).json({
346
- success: false,
347
- error: 'Forbidden',
348
- message: 'Access to the requested resource is forbidden',
349
- timestamp: new Date().toISOString()
350
- });
351
- });
352
-
353
- const server = app.listen(port, serverAddress, (err) => {
354
- if (err) {
355
- console.error(\`Failed to start \${moduleNameCapitalized} server:\`, err);
356
- return;
357
- }
358
-
359
- // Determine display URL based on serverAddress
360
- const displayHost = (serverAddress === '0.0.0.0' || !serverAddress) ? 'localhost' : serverAddress;
361
-
362
- logServerReady({
363
- port,
364
- module: '${moduleName}',
365
- healthCheck: \`http://\${displayHost}:\${port}/api/${moduleName}/health\`,
366
- serviceInfo: \`http://\${displayHost}:\${port}/api/${moduleName}/info\`,
367
- baseUrl: \`http://\${displayHost}:\${port}\`
368
- });
369
- console.log('');
370
-
371
- // Execute plugin onAfterServerStart hook (jika ada)
372
- if (plugin && plugin.onAfterServerStart) {
373
- try {
374
- plugin.onAfterServerStart(app, config);
375
- } catch (pluginError) {
376
- logger.error({ event: 'plugin_after_start_error', error: pluginError.message }, 'Plugin onAfterServerStart failed');
377
- }
378
- }
379
- });
380
-
381
- process.on('SIGINT', () => {
382
- console.log('Menerima sinyal SIGINT, shutting down...');
383
- process.exit(0);
384
- });
385
-
386
- process.on('SIGTERM', () => {
387
- console.log('Menerima sinyal SIGTERM, shutting down...');
388
- process.exit(0);
389
- });
390
-
391
- process.on('uncaughtException', (error) => {
392
- console.error('Uncaught Exception:', error);
393
- });
394
-
395
- process.on('unhandledRejection', (reason, promise) => {
396
- console.error('Unhandled Rejection at:', promise, 'reason:', reason);
397
- });
398
-
399
- } catch (error) {
400
- console.error('Error saat menjalankan modul ${moduleName}:', error);
401
- resolve();
402
- }
403
- });
404
- }
405
-
406
- module.exports = { execute };`;
407
- }
408
-
409
- /**
410
- * Build SQL condition string dari defaultScope filter object (Oracle).
411
- * Oracle menyimpan boolean sebagai VARCHAR2 string 'true'/'false' (bukan 1/0).
412
- * Konsisten dengan formatBooleanValue() di upsert-builder.js.
413
- * Dijalankan di generation time — output di-hardcode ke generated module.
414
- * @param {Object} scopeFilter - Object filter, misal { is_active: true }
415
- * @param {string} [prefix=''] - Prefix kolom (misal 'a.' atau '')
416
- * @returns {string} SQL condition string, misal "is_active = 'true'"
417
- */
418
- function buildDefaultScopeSQL(scopeFilter, prefix = '') {
419
- if (!scopeFilter || typeof scopeFilter !== 'object') return '';
420
- const conditions = [];
421
- for (const [col, val] of Object.entries(scopeFilter)) {
422
- if (typeof val === 'boolean') {
423
- conditions.push(`${prefix}${col} = '${val}'`);
424
- } else if (typeof val === 'string') {
425
- conditions.push(`${prefix}${col} = '${val.replace(/'/g, "''")}'`);
426
- } else if (typeof val === 'number') {
427
- conditions.push(`${prefix}${col} = ${val}`);
428
- }
429
- }
430
- return conditions.join(' AND ');
431
- }
432
-
433
- /**
434
- * Generate request scope middleware code (Layer 1 RLS) for Oracle template.
435
- * Middleware injected between validation middleware and route handlers.
436
- * Runs at GENERATION TIME to emit code string executed at RUNTIME.
437
- *
438
- * Mirrors postgres-template.js and mysql-template.js behavior — middleware
439
- * emits JS (not SQL), identical across dialects (see spec Bagian 4.1).
440
- *
441
- * @param {Object} payload - Endpoint payload JSON
442
- * @param {string} endpointName - Endpoint name (used to compute composite rootKey)
443
- * @returns {string} Generated middleware code, or empty string if requestScope absent
444
- */
445
- function buildRequestScopeMiddleware(payload, endpointName) {
446
- if (!payload.requestScope) return '';
447
-
448
- const { column, source, bypassRoles } = payload.requestScope;
449
- const bypassRolesJSON = JSON.stringify(bypassRoles || []);
450
- // Composite endpoints wrap master data under req.body[rootKey]
451
- // (rootKey mirrors the endpoint name with dashes replaced by underscores).
452
- const rootKey = (endpointName || '').replace(/-/g, '_');
453
-
454
- return `
455
- // ─── Request Scope Middleware (Layer 1 RLS) ──────────────────────────
456
- // Dynamic per-request filtering by '${column}' from ${source}
457
- // Generated from requestScope configuration in payload JSON
458
- router.use((req, res, next) => {
459
- if (req.method !== 'POST') return next();
460
-
461
- // Skip when no auth context (endpoint without auth middleware)
462
- if (!req.user) return next();
463
-
464
- // Skip when user has bypass role
465
- const userRoles = req.user.roles || [];
466
- const bypassRoles = ${bypassRolesJSON};
467
- if (bypassRoles.length > 0 && bypassRoles.some(role => userRoles.includes(role))) {
468
- return next();
469
- }
470
-
471
- // Read scope value from user context
472
- const scopeValue = ${source};
473
- if (scopeValue === undefined || scopeValue === null) {
474
- return res.status(403).json({
475
- success: false,
476
- error: 'Forbidden',
477
- message: 'Request scope value not available in user context',
478
- timestamp: new Date().toISOString()
479
- });
480
- }
481
-
482
- // Expose scope info for route handlers (ownership verification in /first, /update, /update-composite)
483
- req._requestScope = { column: '${column}', value: scopeValue };
484
-
485
- const endpoint = req.path.substring(1);
486
- const scopeCondition = { key: '${column}', value: scopeValue };
487
-
488
- // READ operations: inject scope into req.body.where
489
- if (['datatables', 'read', 'lookup', 'read-composite'].includes(endpoint)) {
490
- if (!req.body.where) {
491
- req.body.where = [scopeCondition];
492
- } else if (Array.isArray(req.body.where)) {
493
- req.body.where.unshift(scopeCondition);
494
- } else if (req.body.where.conditions && Array.isArray(req.body.where.conditions)) {
495
- const originalLogic = req.body.where.logic || 'AND';
496
- if (originalLogic === 'AND') {
497
- req.body.where.conditions.unshift(scopeCondition);
498
- } else {
499
- req.body.where = {
500
- logic: 'AND',
501
- conditions: [
502
- scopeCondition,
503
- { logic: originalLogic, conditions: req.body.where.conditions }
504
- ]
505
- };
506
- }
507
- }
508
- }
509
-
510
- // DELETE: prepend scope condition to where array
511
- if (endpoint === 'delete') {
512
- if (req.body.where) {
513
- if (Array.isArray(req.body.where)) {
514
- req.body.where.unshift(scopeCondition);
515
- }
516
- }
517
- }
518
-
519
- // CREATE / ADD: force scope column value
520
- if (endpoint === 'add' || endpoint === 'create') {
521
- req.body['${column}'] = scopeValue;
522
- }
523
-
524
- // UPDATE: force scope column value (ownership verified in /update handler)
525
- if (endpoint === 'update') {
526
- req.body['${column}'] = scopeValue;
527
- }
528
-
529
- // CREATE-COMPOSITE / UPDATE-COMPOSITE: force scope column inside master body.
530
- if (endpoint === 'create-composite' || endpoint === 'update-composite') {
531
- const masterBody = req.body['${rootKey}'] || req.body;
532
- masterBody['${column}'] = scopeValue;
533
- }
534
-
535
- next();
536
- });
537
- `;
538
- }
539
-
540
- /**
541
- * Membuat template untuk model Oracle
542
- * @param {string} moduleName - Nama module
543
- * @param {string} endpointName - Nama endpoint
544
- * @param {Object} payload - Data payload dari JSON
545
- * @returns {string} Template model untuk Oracle
546
- */
547
- function createOracleModelTemplate(moduleName, endpointName, payload) {
548
- const validFields = payload.fieldName.map(field => `'${field}'`).join(', ');
549
- const className = toPascalCase(endpointName);
550
- const primaryKey = payload.primaryKey || 'id';
551
- const textColumn = detectTextColumn(payload.fieldName);
552
- const timestamp = new Date().toISOString();
553
- const auditColumnsSection = buildAuditColumnsSection(payload, ' ');
554
-
555
- // Build default scope SQL (generation time, Oracle: boolean = 1/0)
556
- const lookupScopeSQL = payload.defaultScope && payload.defaultScope.lookup
557
- ? buildDefaultScopeSQL(payload.defaultScope.lookup)
558
- : '';
559
- const readScopeSQL = payload.defaultScope && payload.defaultScope.read
560
- ? buildDefaultScopeSQL(payload.defaultScope.read)
561
- : '';
562
-
563
- // Generate dateTimeFields dari fieldValidation
564
- const dateTimeFields = {};
565
- if (payload.fieldValidation && Array.isArray(payload.fieldValidation)) {
566
- payload.fieldValidation.forEach(field => {
567
- if (['date', 'datetime', 'timestamp', 'time'].includes(field.type)) {
568
- dateTimeFields[field.name] = {
569
- type: field.type,
570
- format: (field.constraints && field.constraints.format) || 'yyyy-MM-dd'
571
- };
572
- }
573
- });
574
- }
575
-
576
- return `const BaseModel = require('@restforgejs/platform/src/models/base-model-oracle');
577
- const db = require('@restforgejs/platform/src/utils/db-oracle');
578
- const oracledb = require('oracledb');
579
- const fs = require('fs');
580
- const path = require('path');
581
-
582
- /**
583
- * ${className} Model - Oracle Database
584
- * Generated: ${timestamp}
585
- *
586
- * Table: ${payload.tableName}
587
- * Primary Key: ${primaryKey}
588
- * Fields: ${payload.fieldName.length}
589
- * Database: Oracle
590
- */
591
- class ${className}Model extends BaseModel {
592
- /**
593
- * Constructor
594
- */
595
- constructor() {
596
- const validFields = [
597
- ${validFields}
598
- ];
599
-
600
- const datatablesWhere = ${payload.datatablesWhere ? JSON.stringify(payload.datatablesWhere) : "['kode', 'nama', 'all']"};
601
-
602
- super('${payload.tableName}', validFields);
603
-
604
- // base-model-oracle hanya menerima 2 parameter, simpan datatablesWhere manual
605
- this.datatablesWhere = datatablesWhere;
606
-
607
- // Primary key configuration
608
- this.primaryKey = '${primaryKey}';
609
-
610
- // Read/Write source configuration
611
- this.viewName = '${payload.viewName || payload.tableName}';
612
- this.readSource = '${payload.viewName || payload.tableName}';
613
- this.writeSource = '${payload.tableName}';
614
- ${auditColumnsSection}
615
- // Flag untuk self-documenting API (endpoint /info)
616
- this.hasViewQuery = ${!!payload.viewQuery};
617
- this.hasExportQuery = ${!!payload.exportQuery};
618
- ${Object.keys(dateTimeFields).length > 0 ? `
619
- // DateTime fields configuration dari fieldValidation
620
- this.dateTimeFields = ${JSON.stringify(dateTimeFields, null, 4).split('\n').join('\n ')};
621
- ` : ''}${payload.uploadConfig && payload.uploadConfig.fields ? `
622
- // File upload fields (CLOB columns)
623
- this.fileFields = ${JSON.stringify(Object.keys(payload.uploadConfig.fields))};
624
- ` : ''}
625
- // Field validation configuration
626
- ${(() => {
627
- if (!payload.fieldValidation || !Array.isArray(payload.fieldValidation) || payload.fieldValidation.length === 0) {
628
- return ' this.validationConfig = {}; // No field validation config';
629
- }
630
- const configEntries = payload.fieldValidation.map(field => {
631
- const constraints = JSON.stringify(field.constraints || {}, null, 6).replace(/\n/g, '\n ');
632
- return ` '${field.name}': {
633
- type: '${field.type}',
634
- constraints: ${constraints}
635
- }`;
636
- }).join(',\n');
637
- return ` this.validationConfig = {
638
- ${configEntries}
639
- }`;
640
- })()}
641
-
642
- // Model metadata
643
- this.modelMetadata = {
644
- endpointName: '${endpointName}',
645
- moduleName: '${moduleName}',
646
- tableName: '${payload.tableName}',
647
- databaseType: 'oracle',
648
- primaryKey: '${primaryKey}',
649
- fieldCount: ${payload.fieldName.length},
650
- generated: '${timestamp}'
651
- };
652
-
653
- this.advancedQueryTemplates = this.loadAdvancedQueryTemplates();
654
- }
655
-
656
- /**
657
- * Load advanced query templates dari file
658
- */
659
- loadAdvancedQueryTemplates() {
660
- const templates = {};
661
-
662
- ${payload.advancedQueries ? Object.entries(payload.advancedQueries).map(([key, value]) => `
663
- try {
664
- if (typeof "${value}" === 'string' && "${value}".startsWith('file:')) {
665
- const relativePath = "${value}".replace('file:', '');
666
- const filePath = path.join(__dirname, relativePath);
667
-
668
- if (fs.existsSync(filePath)) {
669
- templates["${key}"] = fs.readFileSync(filePath, 'utf8');
670
- console.log(\`SQL Template ${key} loaded successfully from Oracle file\`);
671
- } else {
672
- console.error(\`SQL Template file ${key} not found: \${filePath}\`);
673
- templates["${key}"] = null;
674
- }
675
- } else {
676
- templates["${key}"] = "${value}";
677
- }
678
- } catch (error) {
679
- console.error(\`Error loading Oracle template ${key}:\`, error);
680
- templates["${key}"] = null;
681
- }`).join('') : '// No advanced queries defined'}
682
-
683
- return templates;
684
- }
685
-
686
- /**
687
- * Override getListQuery untuk Oracle syntax
688
- */
689
- getListQuery(options = {}) {
690
- let baseQuery = \`
691
- ${payload.datatablesQuery.replace(/\$\{tableName\}/g, "${this.getTableSource('read')}")}
692
- \`.trim();
693
-
694
- // Convert PostgreSQL syntax to Oracle if needed
695
- baseQuery = this.convertToOracleSQL(baseQuery);
696
-
697
- return baseQuery;
698
- }
699
-
700
- /**
701
- * Override getReadQuery untuk endpoint /read
702
- * Prioritas: viewName → viewQuery → tableName (SELECT * FROM readSource)
703
- */
704
- getReadQuery(options = {}) {
705
- if (this.viewName && this.viewName !== this.table) {
706
- return 'SELECT * FROM ' + this.viewName;
707
- }
708
- ${payload.viewQuery
709
- ? ` let baseQuery = \`
710
- ${payload.viewQuery.replace(/\$\{tableName\}/g, "${this.getTableSource('read')}")}
711
- \`.trim();
712
- baseQuery = this.convertToOracleSQL(baseQuery);
713
- return baseQuery;`
714
- : ` return 'SELECT * FROM ' + this.readSource;`
715
- }
716
- }
717
-
718
- /**
719
- * Convert PostgreSQL SQL syntax to Oracle
720
- */
721
- convertToOracleSQL(sql) {
722
- sql = sql.replace(/\\bILIKE\\b/gi, 'LIKE');
723
- sql = sql.replace(/LIMIT\\s+(\\d+)\\s+OFFSET\\s+(\\d+)/gi, (match, limit, offset) => {
724
- return \`AND ROWNUM BETWEEN \${parseInt(offset) + 1} AND \${parseInt(offset) + parseInt(limit)}\`;
725
- });
726
- sql = sql.replace(/NOW\\(\\)/gi, 'SYSDATE');
727
- sql = sql.replace(/CURRENT_DATE/gi, 'SYSDATE');
728
- return sql;
729
- }
730
-
731
- /**
732
- * Override getDatatables untuk Oracle dengan pagination yang tepat
733
- * Paritas fungsional dengan PostgreSQL getDatatables
734
- */
735
- async getDatatables(options) {
736
- try {
737
- // Check cache first
738
- const cachedResult = await this.getCachedDatatables(options);
739
- if (cachedResult) return cachedResult;
740
-
741
- const {
742
- searchValue = '',
743
- searchBy = 'all',
744
- perPage = 10,
745
- start = 0,
746
- sort_columns = [],
747
- filters = {},
748
- advancedFilters = []
749
- } = options;
750
-
751
- // Resolve sort columns dengan prioritas: sort_columns > order[0][column] > default
752
- let resolvedSortColumns = sort_columns;
753
-
754
- // Fallback: cek format DataTables bawaan (order[0][column] dan order[0][dir])
755
- if ((!resolvedSortColumns || resolvedSortColumns.length === 0) &&
756
- options['order[0][column]'] !== undefined && options['order[0][dir]'] !== undefined) {
757
- const columnIndex = parseInt(options['order[0][column]']);
758
- const direction = options['order[0][dir]'];
759
-
760
- if (columnIndex >= 0 && columnIndex < this.validFields.length) {
761
- resolvedSortColumns = [{ column: this.validFields[columnIndex], direction: direction.toUpperCase() }];
762
- }
763
- }
764
-
765
- const orderClause = this.buildSortColumnsClause(resolvedSortColumns);
766
-
767
- const baseQuery = this.getListQuery(options);
768
-
769
- // Build WHERE clause (parameterized)
770
- const searchResult = this.buildWhereClause(searchValue, searchBy);
771
- let whereClauseSql = searchResult.sql;
772
- let whereParams = [...searchResult.params];
773
-
774
- // Build filter clause
775
- const filterClause = this.buildObjectFilterClause(filters);
776
- if (filterClause) {
777
- if (whereClauseSql) {
778
- whereClauseSql = \`\${whereClauseSql} AND \${filterClause}\`;
779
- } else {
780
- whereClauseSql = \`WHERE \${filterClause}\`;
781
- }
782
- }
783
-
784
- // Support WHERE conditions dari request body
785
- if (options.where) {
786
- try {
787
- const complexResult = this.buildComplexWhereClause(options.where, whereParams, whereParams.length + 1);
788
- if (whereClauseSql) {
789
- whereClauseSql = \`\${whereClauseSql} AND \${complexResult.sql}\`;
790
- } else {
791
- whereClauseSql = \`WHERE \${complexResult.sql}\`;
792
- }
793
- whereParams = complexResult.params;
794
- } catch (e) {
795
- const error = new Error('Invalid where conditions: ' + e.message);
796
- error.statusCode = 400;
797
- throw error;
798
- }
799
- }
800
-
801
- // Advanced filters support
802
- if (advancedFilters && advancedFilters.length > 0) {
803
- const advResult = this.buildAdvancedFilterCondition(advancedFilters);
804
- if (advResult.sql) {
805
- if (whereClauseSql) {
806
- whereClauseSql = \`\${whereClauseSql} AND \${advResult.sql}\`;
807
- } else {
808
- whereClauseSql = \`WHERE \${advResult.sql}\`;
809
- }
810
- whereParams.push(...advResult.params);
811
- }
812
- }
813
-
814
- // Check if query needs subquery wrapping (CTE or JOIN)
815
- const isCteQuery = baseQuery.toLowerCase().trim().startsWith('with');
816
- const hasJoin = /\\b(inner|left|right|cross|full)\\s+join\\b/i.test(baseQuery) || /\\bjoin\\b/i.test(baseQuery);
817
- const needsSubquery = isCteQuery || hasJoin;
818
-
819
- // Count total records
820
- const countTotalQuery = needsSubquery ?
821
- \`SELECT COUNT(*) as TOTAL FROM (\${baseQuery}) base_query\` :
822
- 'SELECT COUNT(*) as TOTAL FROM ' + this.getTableSource('read') + ' a';
823
- const countTotalResult = await db.executeQuery(countTotalQuery);
824
- const totalRecords = countTotalResult && countTotalResult[0] ? parseInt(countTotalResult[0].TOTAL) : 0;
825
-
826
- // Count filtered records
827
- let filteredRecords = totalRecords;
828
- if (whereClauseSql) {
829
- const countFilteredQuery = needsSubquery ?
830
- \`SELECT COUNT(*) as TOTAL FROM (\${baseQuery}) base_query \${whereClauseSql}\` :
831
- 'SELECT COUNT(*) as TOTAL FROM ' + this.getTableSource('read') + ' a ' + whereClauseSql;
832
- const countFilteredResult = await db.executeQuery(countFilteredQuery, whereParams.length > 0 ? whereParams : undefined);
833
- filteredRecords = countFilteredResult && countFilteredResult[0] ? parseInt(countFilteredResult[0].TOTAL) : 0;
834
- }
835
-
836
- // Oracle pagination using ROWNUM
837
- const endRow = start + perPage;
838
-
839
- const query = needsSubquery ?
840
- \`SELECT * FROM (SELECT base_query.*, ROWNUM rnum FROM (SELECT * FROM (\${baseQuery}) base_query \${whereClauseSql || ''} \${orderClause}) base_query WHERE ROWNUM <= \${endRow}) WHERE rnum > \${start}\` :
841
- \`SELECT * FROM (SELECT a.*, ROWNUM rnum FROM (\${baseQuery} \${whereClauseSql || ''} \${orderClause}) a WHERE ROWNUM <= \${endRow}) WHERE rnum > \${start}\`;
842
-
843
- console.log('Final Query:', query);
844
- console.log('Query Parameters:', whereParams.length > 0 ? whereParams : []);
845
- const rawData = await db.executeQuery(query, whereParams.length > 0 ? whereParams : undefined);
846
-
847
- // Format data: hapus RNUM, normalize ke lowercase, tambahkan ROWNUMERATOR
848
- const data = rawData ? rawData.map((row, index) => {
849
- const { RNUM, rnum, ...cleanRow } = row;
850
- const formatted = this.formatResponseData(cleanRow);
851
- return {
852
- ...formatted,
853
- ROWNUMERATOR: start + index + 1
854
- };
855
- }) : [];
856
-
857
- const result = {
858
- draw: parseInt(options.draw || '1', 10),
859
- recordsTotal: totalRecords,
860
- recordsFiltered: filteredRecords,
861
- data: data
862
- };
863
-
864
- // Cache result
865
- await this.setCachedDatatables(options, result);
866
-
867
- return result;
868
- } catch (error) {
869
- console.error('Error in getDatatables:', error);
870
- throw error;
871
- }
872
- }
873
-
874
- /**
875
- * Build WHERE clause untuk search (parameterized query)
876
- * @returns {Object} { sql: string, params: array }
877
- */
878
- buildWhereClause(searchValue, searchBy) {
879
- if (!searchValue || searchValue === '') {
880
- return { sql: '', params: [] };
881
- }
882
-
883
- const params = [];
884
- let paramIndex = 1;
885
- const searchPattern = \`%\${searchValue}%\`;
886
-
887
- if (searchBy === 'all') {
888
- const searchableFields = this.datatablesWhere.filter(field => field !== 'all');
889
- if (searchableFields.length > 0) {
890
- const conditions = searchableFields.map(field => {
891
- params.push(searchPattern);
892
- return \`UPPER(\${field}) LIKE UPPER(:\${paramIndex++})\`;
893
- });
894
- return { sql: \`WHERE (\${conditions.join(' OR ')})\`, params };
895
- }
896
- } else if (this.validFields.includes(searchBy)) {
897
- params.push(searchPattern);
898
- return { sql: \`WHERE UPPER(\${searchBy}) LIKE UPPER(:1)\`, params };
899
- }
900
-
901
- return { sql: '', params: [] };
902
- }
903
-
904
- /**
905
- * Build filter clause dari object filters
906
- * @param {Object} filters - Filter object {column: value}
907
- * @returns {string} Filter conditions SQL (tanpa WHERE prefix)
908
- */
909
- buildObjectFilterClause(filters) {
910
- if (!filters || typeof filters !== 'object' || Object.keys(filters).length === 0) {
911
- return '';
912
- }
913
-
914
- const conditions = [];
915
- for (const [column, value] of Object.entries(filters)) {
916
- if (!this.validFields.includes(column)) continue;
917
- if (value === null || value === undefined || value === '' || value === 'all' || value === '-') continue;
918
-
919
- const escapedValue = value.toString().replace(/'/g, "''");
920
- conditions.push(\`\${column} = '\${escapedValue}'\`);
921
- }
922
-
923
- return conditions.length > 0 ? conditions.join(' AND ') : '';
924
- }
925
-
926
- /**
927
- * Get list data dengan pagination untuk Oracle
928
- */
929
- async getList(options) {
930
- try {
931
- // Check cache first
932
- const cachedResult = await this.getCachedList(options);
933
- if (cachedResult) {
934
- const { page: p = null, perPage: pp = 10, searchValue: sv = '', sort_columns: sc = [], where: w = null } = options;
935
- const scInfo = sc && sc.length > 0 ? sc.map(s => \`\${s.column}:\${s.direction}\`).join(',') : 'default';
936
- console.log(\`[Cache] HIT for list - page:\${p}, perPage:\${pp}, sort:\${scInfo}, search:\${sv || 'none'}\${w ? ', where:yes' : ''}\`);
937
- return cachedResult;
938
- }
939
-
940
- const {
941
- page = null,
942
- perPage = 10,
943
- searchValue = '',
944
- searchBy = 'all',
945
- sort_columns = [],
946
- where = null,
947
- select = null,
948
- limit = 1000
949
- } = options;
950
-
951
- const paginate = page !== null;
952
- const scInfo = sort_columns && sort_columns.length > 0 ? sort_columns.map(s => \`\${s.column}:\${s.direction}\`).join(',') : 'default';
953
- const cacheInfo = \`page:\${page}, perPage:\${perPage}, sort:\${scInfo}, search:\${searchValue || 'none'}\${where ? ', where:yes' : ''}\`;
954
-
955
- console.log(\`[Cache] MISS for list - \${cacheInfo}\`);
956
-
957
- // 1. Mendapatkan query dasar
958
- let baseQuery;
959
- if (select && Array.isArray(select) && select.length > 0) {
960
- const selectedValidColumns = select.filter(col => this.validFields.includes(col));
961
- if (selectedValidColumns.length > 0) {
962
- baseQuery = 'SELECT ' + selectedValidColumns.join(', ') + ' FROM ' + this.getTableSource('read') + ' a';
963
- } else {
964
- baseQuery = 'SELECT * FROM ' + this.getTableSource('read') + ' a';
965
- }
966
- } else {
967
- baseQuery = this.getReadQuery(options);
968
- }
969
-
970
- // Deteksi apakah query mengandung JOIN atau CTE (perlu subquery wrapping)
971
- const isCteQuery = baseQuery.toLowerCase().trim().startsWith('with');
972
- const hasJoin = /\\b(inner|left|right|cross|full)\\s+join\\b/i.test(baseQuery) || /\\bjoin\\b/i.test(baseQuery);
973
- const needsSubquery = isCteQuery || hasJoin;
974
-
975
- const searchResult = this.buildWhereClause(searchValue, searchBy);
976
- let whereClauseSql = searchResult.sql;
977
- let whereParams = [...searchResult.params];
978
- const orderClause = this.buildSortColumnsClause(sort_columns);
979
- ${readScopeSQL ? `
980
- // Default scope filter untuk read
981
- if (whereClauseSql) {
982
- whereClauseSql = \`WHERE ${readScopeSQL} AND \` + whereClauseSql.replace(/^WHERE\\s+/i, '');
983
- } else {
984
- whereClauseSql = 'WHERE ${readScopeSQL}';
985
- }
986
- ` : ''}
987
- // Support WHERE conditions dari request body
988
- if (where) {
989
- try {
990
- const complexResult = this.buildComplexWhereClause(where, whereParams, whereParams.length + 1);
991
- if (whereClauseSql) {
992
- whereClauseSql = \`\${whereClauseSql} AND \${complexResult.sql}\`;
993
- } else {
994
- whereClauseSql = \`WHERE \${complexResult.sql}\`;
995
- }
996
- whereParams = complexResult.params;
997
- } catch (e) {
998
- const error = new Error('Invalid where conditions: ' + e.message);
999
- error.statusCode = 400;
1000
- throw error;
1001
- }
1002
- }
1003
-
1004
- // Count total unfiltered records
1005
- const countTotalQuery = needsSubquery
1006
- ? 'SELECT COUNT(*) as TOTAL FROM (' + baseQuery + ') base_query'
1007
- : 'SELECT COUNT(*) as TOTAL FROM ' + this.getTableSource('read') + ' a';
1008
- const countTotalResult = await db.executeQuery(countTotalQuery);
1009
- const totalUnfiltered = countTotalResult && countTotalResult[0] ? parseInt(countTotalResult[0].TOTAL) : 0;
1010
-
1011
- // Count filtered records
1012
- let totalRecords = totalUnfiltered;
1013
- if (whereClauseSql) {
1014
- const countQuery = needsSubquery
1015
- ? 'SELECT COUNT(*) as TOTAL FROM (' + baseQuery + ') base_query ' + (whereClauseSql || '')
1016
- : 'SELECT COUNT(*) as TOTAL FROM ' + this.getTableSource('read') + ' a ' + (whereClauseSql || '');
1017
- const countResult = await db.executeQuery(countQuery, whereParams.length > 0 ? whereParams : undefined);
1018
- totalRecords = countResult && countResult[0] ? parseInt(countResult[0].TOTAL) : 0;
1019
- }
1020
-
1021
- // Build query berdasarkan mode paginasi (subquery wrapping untuk JOIN/CTE)
1022
- let query;
1023
- if (paginate) {
1024
- // Oracle pagination using ROWNUM
1025
- const offset = (page - 1) * perPage;
1026
- const endRow = offset + perPage;
1027
- query = needsSubquery
1028
- ? 'SELECT * FROM (SELECT base_query.*, ROWNUM rnum FROM (SELECT * FROM (' + baseQuery + ') base_query ' + (whereClauseSql || '') + orderClause + ') base_query WHERE ROWNUM <= ' + endRow + ') WHERE rnum > ' + offset
1029
- : 'SELECT * FROM (SELECT a.*, ROWNUM rnum FROM (' + baseQuery + ' ' + (whereClauseSql || '') + orderClause + ') a WHERE ROWNUM <= ' + endRow + ') WHERE rnum > ' + offset;
1030
- } else {
1031
- // Non-paginasi dengan safety limit
1032
- query = needsSubquery
1033
- ? 'SELECT * FROM (SELECT base_query.*, ROWNUM rnum FROM (SELECT * FROM (' + baseQuery + ') base_query ' + (whereClauseSql || '') + orderClause + ') base_query WHERE ROWNUM <= ' + limit + ')'
1034
- : 'SELECT * FROM (SELECT a.*, ROWNUM rnum FROM (' + baseQuery + ' ' + (whereClauseSql || '') + orderClause + ') a WHERE ROWNUM <= ' + limit + ')';
1035
- }
1036
-
1037
- console.log('List SQL Query:', query);
1038
- console.log('List Query Parameters:', whereParams);
1039
- const rawData = await db.executeQuery(query, whereParams.length > 0 ? whereParams : undefined);
1040
-
1041
- const data = rawData ? rawData.map((row) => {
1042
- const { RNUM, rnum, ...cleanRow } = row;
1043
- return this.formatResponseData(cleanRow);
1044
- }) : [];
1045
-
1046
- const result = {
1047
- success: true,
1048
- data: data
1049
- };
1050
-
1051
- if (paginate) {
1052
- const totalPages = Math.ceil(totalRecords / perPage);
1053
- result.pagination = {
1054
- current_page: page,
1055
- per_page: perPage,
1056
- total_records: totalRecords,
1057
- total_pages: totalPages,
1058
- has_next: page < totalPages,
1059
- has_previous: page > 1
1060
- };
1061
- }
1062
-
1063
- // Cache result
1064
- await this.setCachedList(options, result);
1065
- console.log(\`[Cache] SET for list - \${cacheInfo}\`);
1066
-
1067
- return result;
1068
- } catch (error) {
1069
- console.error('Error in getList:', error);
1070
- throw error;
1071
- }
1072
- }
1073
-
1074
- /**
1075
- * Override getLookupData untuk Oracle (dynamic search)
1076
- */
1077
- async getLookupData(search) {
1078
- try {
1079
- const query = \`SELECT * FROM (SELECT ${primaryKey}, ${textColumn} FROM \${this.getTableSource('read')} WHERE UPPER(${textColumn}) LIKE UPPER(:1)${lookupScopeSQL ? ` AND ${lookupScopeSQL}` : ''} ORDER BY ${textColumn}) WHERE ROWNUM <= 100\`;
1080
- const params = [\`%\${search || ''}%\`];
1081
- const data = await db.executeQuery(query, params);
1082
-
1083
- const result = data.map(item => ({
1084
- id: item.${primaryKey.toUpperCase()},
1085
- text: item.${textColumn.toUpperCase()}
1086
- }));
1087
-
1088
- return result;
1089
- } catch (error) {
1090
- console.error('Error in Oracle getLookupData:', error);
1091
- throw error;
1092
- }
1093
- }
1094
-
1095
- /**
1096
- * Dynamic lookup dengan extra filters untuk Oracle
1097
- */
1098
- async getLookupDataDynamic(search, extraFilters = {}) {
1099
- try {
1100
- let params = [];
1101
- let paramIndex = 1;
1102
- let whereConditions = [];
1103
- ${lookupScopeSQL ? `\n // Default scope filter\n whereConditions.push('${lookupScopeSQL}');\n` : ''}
1104
- if (search) {
1105
- whereConditions.push(\`UPPER(${textColumn}) LIKE UPPER(:\${paramIndex})\`);
1106
- params.push(\`%\${search}%\`);
1107
- paramIndex++;
1108
- }
1109
-
1110
- // Add extra filters
1111
- if (extraFilters && Object.keys(extraFilters).length > 0) {
1112
- for (const [key, value] of Object.entries(extraFilters)) {
1113
- if (this.validFields.includes(key) && value !== null && value !== undefined) {
1114
- whereConditions.push(\`\${key} = :\${paramIndex}\`);
1115
- params.push(value);
1116
- paramIndex++;
1117
- }
1118
- }
1119
- }
1120
-
1121
- const whereClause = whereConditions.length > 0 ? 'WHERE ' + whereConditions.join(' AND ') : '';
1122
-
1123
- const query = \`SELECT * FROM (SELECT ${primaryKey}, ${textColumn} FROM \${this.getTableSource('read')} \${whereClause} ORDER BY ${textColumn}) WHERE ROWNUM <= 100\`;
1124
- const data = await db.executeQuery(query, params.length > 0 ? params : undefined);
1125
-
1126
- return data.map(item => ({
1127
- id: item.${primaryKey.toUpperCase()},
1128
- text: item.${textColumn.toUpperCase()}
1129
- }));
1130
- } catch (error) {
1131
- console.error('Error in Oracle getLookupDataDynamic:', error);
1132
- throw error;
1133
- }
1134
- }
1135
-
1136
- /**
1137
- * Override getStaticLookupData untuk Oracle
1138
- */
1139
- async getStaticLookupData(selectedTag) {
1140
- try {
1141
- // Check cache first - cache tanpa selectedTag karena data sama
1142
- const cacheOptions = { type: 'static' };
1143
- const cachedResult = await this.getCachedLookup(cacheOptions, 'static');
1144
- if (cachedResult) {
1145
- // Apply selectedTag to cached result
1146
- return cachedResult.map(item => {
1147
- if (item.id === selectedTag) {
1148
- return { ...item, selected: 'true' };
1149
- }
1150
- return { id: item.id, text: item.text };
1151
- });
1152
- }
1153
-
1154
- const query = \`SELECT * FROM (SELECT ${primaryKey}, ${textColumn} FROM \${this.getTableSource('read')}${lookupScopeSQL ? ` WHERE ${lookupScopeSQL}` : ''} ORDER BY ${textColumn}) WHERE ROWNUM <= 1000\`;
1155
- const data = await db.executeQuery(query);
1156
-
1157
- // Cache result tanpa selected flag
1158
- const cacheData = data.map(item => ({
1159
- id: item.${primaryKey.toUpperCase()},
1160
- text: item.${textColumn.toUpperCase()}
1161
- }));
1162
- await this.setCachedLookup(cacheOptions, cacheData, 'static');
1163
-
1164
- // Return dengan selected flag
1165
- return data.map(item => {
1166
- const row = {
1167
- id: item.${primaryKey.toUpperCase()},
1168
- text: item.${textColumn.toUpperCase()}
1169
- };
1170
- if (item.${primaryKey.toUpperCase()} === selectedTag) {
1171
- row.selected = 'true';
1172
- }
1173
- return row;
1174
- });
1175
- } catch (error) {
1176
- console.error('Error in Oracle getStaticLookupData:', error);
1177
- throw error;
1178
- }
1179
- }
1180
-
1181
- /**
1182
- * Lookup dengan advanced filter support untuk Oracle
1183
- */
1184
- async getLookupDataWithFilter(options) {
1185
- try {
1186
- // Check cache first
1187
- const cacheOptions = { ...options, type: 'filter' };
1188
- const cachedResult = await this.getCachedLookup(cacheOptions, 'filter');
1189
- if (cachedResult) return cachedResult;
1190
-
1191
- const selectColumns = options.select || ['${primaryKey}', '${textColumn}'];
1192
- let params = [];
1193
- let paramIndex = 1;
1194
-
1195
- // Validasi text fields
1196
- const validTextFields = this.validFields.filter(field =>
1197
- field.includes('name') || field.includes('nama') ||
1198
- field.includes('code') || field.includes('kode') ||
1199
- field.includes('text') || field.includes('title')
1200
- );
1201
-
1202
- let selectClause = '${primaryKey}';
1203
- let textField = '${textColumn}';
1204
- let aliasField = null;
1205
-
1206
- // Proses setiap column dalam select
1207
- for (const column of selectColumns) {
1208
- if (column.toLowerCase() === '${primaryKey}') {
1209
- continue; // primary key sudah ada
1210
- }
1211
-
1212
- // Check jika ada SQL expression dengan alias (menggunakan AS)
1213
- const aliasRegex = new RegExp('(.+)\\\\s+as\\\\s+(\\\\w+)$', 'i');
1214
- const aliasMatch = column.match(aliasRegex);
1215
- if (aliasMatch) {
1216
- const expression = aliasMatch[1].trim();
1217
- const alias = aliasMatch[2].trim();
1218
- selectClause += \`, \${expression} AS \${alias}\`;
1219
- textField = alias;
1220
- aliasField = alias;
1221
- break;
1222
- }
1223
-
1224
- // Check jika simple field name
1225
- if (this.validFields.includes(column) || validTextFields.includes(column)) {
1226
- selectClause += \`, \${column}\`;
1227
- textField = column;
1228
- break;
1229
- }
1230
-
1231
- // Computed column
1232
- selectClause += \`, \${column}\`;
1233
- textField = column;
1234
- }
1235
-
1236
- let query = \`SELECT \${selectClause} FROM \${this.getTableSource('read')} ${lookupScopeSQL ? `WHERE ${lookupScopeSQL} ` : ''}\`;
1237
-
1238
- // Build WHERE clause jika ada
1239
- if ((options.where && Array.isArray(options.where) && options.where.length > 0) ||
1240
- (options.where && options.where.conditions && Array.isArray(options.where.conditions) && options.where.conditions.length > 0)) {
1241
- try {
1242
- const whereResult = this.buildComplexWhereClause(options.where, params, paramIndex);
1243
- query += \`${lookupScopeSQL ? 'AND (' : 'WHERE '}\${whereResult.sql}${lookupScopeSQL ? ')' : ''} \`;
1244
- params = whereResult.params;
1245
- } catch (e) {
1246
- const error = new Error('Invalid where conditions: ' + e.message);
1247
- error.statusCode = 400;
1248
- throw error;
1249
- }
1250
- }
1251
-
1252
- // Handle sort_columns
1253
- if (options.sort_columns && Array.isArray(options.sort_columns) && options.sort_columns.length > 0) {
1254
- const orderParts = options.sort_columns.map(item => {
1255
- const column = item.column;
1256
- const direction = (item.direction || 'ASC').toUpperCase();
1257
- if (!column) return null;
1258
- if (!this.validFields.includes(column)) return null;
1259
- if (direction !== 'ASC' && direction !== 'DESC') return null;
1260
- return \`\${column} \${direction}\`;
1261
- }).filter(Boolean);
1262
-
1263
- if (orderParts.length === 0) {
1264
- const error = new Error('No valid sort columns provided');
1265
- error.statusCode = 400;
1266
- throw error;
1267
- }
1268
- query += \`ORDER BY \${orderParts.join(', ')}\`;
1269
- } else {
1270
- query += \`ORDER BY \${aliasField || textField}\`;
1271
- }
1272
-
1273
- console.log('Oracle Lookup Filter Query:', query);
1274
- console.log('Parameters:', params);
1275
-
1276
- const data = await db.executeQuery(query, params.length > 0 ? params : undefined);
1277
-
1278
- const textFieldUpper = (aliasField || textField).toUpperCase();
1279
- const result = data.map(item => ({
1280
- id: item.${primaryKey.toUpperCase()},
1281
- text: item[textFieldUpper] || item.${textColumn.toUpperCase()} || ''
1282
- }));
1283
-
1284
- // Cache the result
1285
- await this.setCachedLookup(cacheOptions, result, 'filter');
1286
-
1287
- return result;
1288
- } catch (error) {
1289
- console.error('Error in getLookupDataWithFilter:', error);
1290
- throw error;
1291
- }
1292
- }
1293
-
1294
- /**
1295
- * Build advanced filter conditions untuk Oracle
1296
- * @param {Array} filters - Array of {column, type, value, value2}
1297
- * @returns {Object} {sql, params}
1298
- */
1299
- buildAdvancedFilterCondition(filters) {
1300
- if (!filters || !Array.isArray(filters) || filters.length === 0) {
1301
- return { sql: '', params: [] };
1302
- }
1303
-
1304
- const conditions = [];
1305
- const params = [];
1306
- let paramIndex = 100; // Start dari 100 agar tidak bentrok dengan param lain
1307
-
1308
- for (const filter of filters) {
1309
- const { column, type, value, value2 } = filter;
1310
-
1311
- if (!column || !this.validFields.includes(column)) continue;
1312
-
1313
- switch (type) {
1314
- case 'equals':
1315
- conditions.push(\`\${column} = :\${paramIndex}\`);
1316
- params.push(value);
1317
- paramIndex++;
1318
- break;
1319
- case 'not_equals':
1320
- conditions.push(\`\${column} <> :\${paramIndex}\`);
1321
- params.push(value);
1322
- paramIndex++;
1323
- break;
1324
- case 'contains':
1325
- case 'like':
1326
- conditions.push(\`UPPER(\${column}) LIKE UPPER(:\${paramIndex})\`);
1327
- params.push(\`%\${value}%\`);
1328
- paramIndex++;
1329
- break;
1330
- case 'not_contains':
1331
- case 'not_like':
1332
- conditions.push(\`UPPER(\${column}) NOT LIKE UPPER(:\${paramIndex})\`);
1333
- params.push(\`%\${value}%\`);
1334
- paramIndex++;
1335
- break;
1336
- case 'starts_with':
1337
- conditions.push(\`UPPER(\${column}) LIKE UPPER(:\${paramIndex})\`);
1338
- params.push(\`\${value}%\`);
1339
- paramIndex++;
1340
- break;
1341
- case 'ends_with':
1342
- conditions.push(\`UPPER(\${column}) LIKE UPPER(:\${paramIndex})\`);
1343
- params.push(\`%\${value}\`);
1344
- paramIndex++;
1345
- break;
1346
- case 'greater_than':
1347
- conditions.push(\`\${column} > :\${paramIndex}\`);
1348
- params.push(value);
1349
- paramIndex++;
1350
- break;
1351
- case 'less_than':
1352
- conditions.push(\`\${column} < :\${paramIndex}\`);
1353
- params.push(value);
1354
- paramIndex++;
1355
- break;
1356
- case 'greater_equal':
1357
- conditions.push(\`\${column} >= :\${paramIndex}\`);
1358
- params.push(value);
1359
- paramIndex++;
1360
- break;
1361
- case 'less_equal':
1362
- conditions.push(\`\${column} <= :\${paramIndex}\`);
1363
- params.push(value);
1364
- paramIndex++;
1365
- break;
1366
- case 'between':
1367
- conditions.push(\`\${column} BETWEEN :\${paramIndex} AND :\${paramIndex + 1}\`);
1368
- params.push(value, value2);
1369
- paramIndex += 2;
1370
- break;
1371
- case 'in':
1372
- if (Array.isArray(value)) {
1373
- const inPlaceholders = value.map((_, i) => \`:\${paramIndex + i}\`).join(', ');
1374
- conditions.push(\`\${column} IN (\${inPlaceholders})\`);
1375
- params.push(...value);
1376
- paramIndex += value.length;
1377
- }
1378
- break;
1379
- case 'not_in':
1380
- if (Array.isArray(value)) {
1381
- const notInPlaceholders = value.map((_, i) => \`:\${paramIndex + i}\`).join(', ');
1382
- conditions.push(\`\${column} NOT IN (\${notInPlaceholders})\`);
1383
- params.push(...value);
1384
- paramIndex += value.length;
1385
- }
1386
- break;
1387
- case 'is_null':
1388
- conditions.push(\`\${column} IS NULL\`);
1389
- break;
1390
- case 'is_not_null':
1391
- conditions.push(\`\${column} IS NOT NULL\`);
1392
- break;
1393
- case 'date_equals':
1394
- conditions.push(\`TRUNC(\${column}) = TO_DATE(:\${paramIndex}, 'YYYY-MM-DD')\`);
1395
- params.push(value);
1396
- paramIndex++;
1397
- break;
1398
- case 'date_between':
1399
- conditions.push(\`TRUNC(\${column}) BETWEEN TO_DATE(:\${paramIndex}, 'YYYY-MM-DD') AND TO_DATE(:\${paramIndex + 1}, 'YYYY-MM-DD')\`);
1400
- params.push(value, value2);
1401
- paramIndex += 2;
1402
- break;
1403
- case 'date_after':
1404
- conditions.push(\`TRUNC(\${column}) > TO_DATE(:\${paramIndex}, 'YYYY-MM-DD')\`);
1405
- params.push(value);
1406
- paramIndex++;
1407
- break;
1408
- case 'date_before':
1409
- conditions.push(\`TRUNC(\${column}) < TO_DATE(:\${paramIndex}, 'YYYY-MM-DD')\`);
1410
- params.push(value);
1411
- paramIndex++;
1412
- break;
1413
- default:
1414
- break;
1415
- }
1416
- }
1417
-
1418
- if (conditions.length === 0) {
1419
- return { sql: '', params: [] };
1420
- }
1421
-
1422
- return { sql: conditions.join(' AND '), params };
1423
- }
1424
-
1425
- /**
1426
- * Escape value untuk Oracle SQL (sanitization)
1427
- */
1428
- escapeValue(value) {
1429
- if (value === null || value === undefined) return null;
1430
- if (typeof value === 'number') return value;
1431
- return String(value).replace(/'/g, "''");
1432
- }
1433
-
1434
- /**
1435
- * Validasi data sebelum insert/update
1436
- */
1437
- async validateData(data, operation = 'insert') {
1438
- const result = {
1439
- isValid: true,
1440
- errors: [],
1441
- warnings: [],
1442
- sanitizedData: {}
1443
- };
1444
-
1445
- try {
1446
- const hasFieldValidation = this.validationConfig && Object.keys(this.validationConfig).length > 0;
1447
-
1448
- if (hasFieldValidation) {
1449
- // Loop semua field yang ada di validationConfig
1450
- for (const fieldName in this.validationConfig) {
1451
- let value = data[fieldName];
1452
- const config = this.validationConfig[fieldName];
1453
- const constraints = config.constraints || {};
1454
-
1455
- // Auto-generate value jika autoGenerate dan nilai kosong.
1456
- // String dan uuid diperlakukan sama: UUID v7 via uuid package
1457
- // (konsisten lintas dialect; cocok dengan konvensi payload category.json
1458
- // yang memakai type: "string" dengan constraint autoGenerate + primaryKey).
1459
- if (operation === 'insert' && constraints.autoGenerate && (!value || value === '')) {
1460
- if (config.type === 'uuid' || config.type === 'string') {
1461
- value = require('uuid').v7();
1462
- data[fieldName] = value;
1463
- }
1464
- }
1465
-
1466
- // Skip validation jika value kosong dan tidak required
1467
- if (value === undefined || value === null || value === '') {
1468
- if (constraints.required) {
1469
- // Skip: autoGenerate atau primaryKey di insert
1470
- if (operation === 'insert' && (constraints.autoGenerate || constraints.primaryKey)) {
1471
- // OK — akan di-generate otomatis
1472
- }
1473
- // Skip: update partial — field tidak dikirim berarti tidak diubah
1474
- else if (operation === 'update' && value === undefined) {
1475
- // OK — field tidak sedang di-update
1476
- }
1477
- else {
1478
- const message = constraints.requiredMessage || \`Field '\${fieldName}' is required\`;
1479
- result.errors.push(message);
1480
- result.isValid = false;
1481
- }
1482
- }
1483
- continue;
1484
- }
1485
-
1486
- // String field: hash constraint support
1487
- if (config.type === 'string' && typeof value === 'string') {
1488
- let sanitized = value;
1489
- const fieldErrors = [];
1490
- const isHashField = constraints.hash === 'bcrypt';
1491
-
1492
- // Trim
1493
- if (constraints.trim) {
1494
- sanitized = sanitized.trim();
1495
- }
1496
-
1497
- // Case transformation (skip jika hash field)
1498
- if (!isHashField) {
1499
- if (constraints.lowercase) {
1500
- sanitized = sanitized.toLowerCase();
1501
- } else if (constraints.uppercase) {
1502
- sanitized = sanitized.toUpperCase();
1503
- }
1504
- }
1505
-
1506
- // Length validation (validasi plaintext sebelum hash)
1507
- if (constraints.minLength && sanitized.length < constraints.minLength) {
1508
- fieldErrors.push(constraints.minLengthMessage || \`Field '\${fieldName}' must be at least \${constraints.minLength} characters\`);
1509
- }
1510
- if (constraints.maxLength && !isHashField && sanitized.length > constraints.maxLength) {
1511
- fieldErrors.push(constraints.maxLengthMessage || \`Field '\${fieldName}' must not exceed \${constraints.maxLength} characters\`);
1512
- }
1513
-
1514
- // Pattern validation
1515
- if (constraints.pattern) {
1516
- const regex = new RegExp(constraints.pattern);
1517
- if (!regex.test(sanitized)) {
1518
- fieldErrors.push(constraints.patternMessage || \`Field '\${fieldName}' does not match required pattern\`);
1519
- }
1520
- }
1521
-
1522
- // Format validation
1523
- if (constraints.format === 'email' && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(sanitized)) {
1524
- fieldErrors.push(constraints.formatMessage || \`Field '\${fieldName}' has invalid email format\`);
1525
- }
1526
-
1527
- if (fieldErrors.length > 0) {
1528
- result.isValid = false;
1529
- result.errors.push(...fieldErrors);
1530
- }
1531
-
1532
- // Hash transformation (setelah semua validation pass)
1533
- if (isHashField && fieldErrors.length === 0) {
1534
- const bcrypt = require('bcrypt');
1535
- const cost = constraints.hashCost || 10;
1536
- sanitized = await bcrypt.hash(sanitized, cost);
1537
- }
1538
-
1539
- result.sanitizedData[fieldName] = sanitized;
1540
- } else {
1541
- // Non-string field: basic sanitization
1542
- result.sanitizedData[fieldName] = value;
1543
- }
1544
- }
1545
-
1546
- // Validate field yang tidak ada di validationConfig (backward compatibility)
1547
- for (const field of this.validFields) {
1548
- if (!this.validationConfig[field] && data[field] !== undefined && data[field] !== null) {
1549
- if (typeof data[field] === 'string') {
1550
- result.sanitizedData[field] = data[field].trim().replace(/\\0/g, '').substring(0, 4000);
1551
- } else {
1552
- result.sanitizedData[field] = data[field];
1553
- }
1554
- }
1555
- }
1556
- } else {
1557
- // Fallback: Tidak ada fieldValidation - gunakan generic sanitization
1558
- for (const field of this.validFields) {
1559
- const value = data[field];
1560
- if (value !== undefined && value !== null) {
1561
- if (typeof value === 'string') {
1562
- result.sanitizedData[field] = value.trim().replace(/\\0/g, '').substring(0, 4000);
1563
- } else {
1564
- result.sanitizedData[field] = value;
1565
- }
1566
- }
1567
- }
1568
- }
1569
- } catch (error) {
1570
- result.errors.push(\`Validation error: \${error.message}\`);
1571
- result.isValid = false;
1572
- }
1573
-
1574
- return result;
1575
- }
1576
-
1577
- /**
1578
- * Get field mapping information
1579
- */
1580
- getFieldMapping() {
1581
- return {
1582
- allFields: this.validFields,
1583
- primaryKey: this.primaryKey,
1584
- textFields: this.validFields.filter(f => f.includes('name') || f.includes('nama') || f.includes('description')),
1585
- dateFields: this.validFields.filter(f => f.includes('date') || f.includes('time')),
1586
- numericFields: this.validFields.filter(f => f.includes('amount') || f.includes('price') || f.includes('count'))
1587
- };
1588
- }
1589
-
1590
- /**
1591
- * Get Oracle connection info untuk health check
1592
- */
1593
- async getConnectionInfo() {
1594
- try {
1595
- const result = await db.executeQuery('SELECT 1 as TEST_CON FROM DUAL');
1596
- if (result && result.length > 0) {
1597
- return { connected: true, database: 'Oracle', retrievedAt: new Date().toISOString() };
1598
- }
1599
- return null;
1600
- } catch (error) {
1601
- return { connected: false, error: error.message, checkedAt: new Date().toISOString() };
1602
- }
1603
- }
1604
-
1605
- /**
1606
- * Format response data untuk Oracle (field names uppercase)
1607
- */
1608
- formatResponseData(data) {
1609
- if (!data) return null;
1610
-
1611
- const formatted = {};
1612
- ${payload.fieldName.map(field => `formatted.${field} = data.${field.toUpperCase()};`).join('\n ')}
1613
-
1614
- return formatted;
1615
- }
1616
- ${(() => {
1617
- const isMasterDetail = payload.masterDetail && payload.masterDetail.enabled;
1618
- const actions = payload.action || {};
1619
- if (!isMasterDetail || (!actions.createComposite && !actions.updateComposite && !actions.readComposite)) return '';
1620
-
1621
- const detailTable = payload.masterDetail.detailTable;
1622
- const detailTableName = detailTable.split('.').pop();
1623
- const foreignKey = payload.masterDetail.foreignKey;
1624
- const detailPrimaryKey = payload.masterDetail.detailConfig?.primaryKey || `${detailTableName}_id`;
1625
- const headerCalculations = payload.masterDetail.headerCalculations || null;
1626
- const autoCalculateFields = payload.masterDetail.detailConfig?.autoCalculateFields || {};
1627
- const detailQuery = payload.masterDetail.detailConfig?.detailQuery || null;
1628
-
1629
- // Separate autoCalculateFields into "generated" (skip from SQL) vs "calculated" (compute & include in SQL)
1630
- const generatedFieldNames = [];
1631
- const calculatedFieldsConfig = [];
1632
- for (const [fieldName, config] of Object.entries(autoCalculateFields)) {
1633
- if (config.type === 'generated') {
1634
- generatedFieldNames.push(fieldName);
1635
- } else {
1636
- // default → "calculated"
1637
- const parts = (config.formula || '').split('*').map(s => s.trim());
1638
- calculatedFieldsConfig.push({ fieldName, qtyField: parts[0], priceField: parts[1] });
1639
- }
1640
- }
1641
-
1642
- // Extract qty and price field names for header recalculation
1643
- const amountFormula = autoCalculateFields.total_amount?.formula || '';
1644
- const amountFormulaParts = amountFormula.split('*').map(s => s.trim());
1645
- const headerCalcQtyField = headerCalculations?.total_qty?.source?.replace('items.', '') || '';
1646
- const amountQtyField = amountFormulaParts[0] || headerCalcQtyField;
1647
- const amountPriceField = amountFormulaParts[1] || 'unit_price';
1648
-
1649
- // Build calculation code block for "calculated" fields (runtime code)
1650
- const calcBlockCode = calculatedFieldsConfig.map(cf => {
1651
- return ` // Auto-calculate ${cf.fieldName} = ${cf.qtyField} * ${cf.priceField}\n item.${cf.fieldName} = (Number(item.${cf.qtyField}) || 0) * (Number(item.${cf.priceField}) || 0);`;
1652
- }).join('\n');
1653
-
1654
- let code = '';
1655
-
1656
- // createComposite
1657
- if (actions.createComposite) {
1658
- code += `
1659
- /**
1660
- * Composite create - Create header with detail items in a single transaction (Oracle)
1661
- */
1662
- async createComposite(data, eventContext = null) {
1663
- const connection = await db.getConnection();
1664
- try {
1665
- const detailKey = '${detailTableName}';
1666
- const headerData = { ...data };
1667
- delete headerData[detailKey];
1668
-
1669
- // --- Hook: onBeforeCompositeInsert ---
1670
- if (eventContext && eventContext.componentEngine) {
1671
- var _ce = eventContext.componentEngine;
1672
- var _CB = eventContext.ContextBuilder;
1673
- var _detailItems = data[detailKey] || [];
1674
- var _beforeCtx = _CB.buildCompositeInsertBeforeContext(headerData, _detailItems, {
1675
- tableName: '${payload.tableName}',
1676
- detailTable: '${detailTable}',
1677
- foreignKey: '${foreignKey}',
1678
- ...(eventContext.additionalContext || {})
1679
- });
1680
- var _beforeResult = await _ce.executeOnBeforeComposite('insert', _beforeCtx);
1681
- if (!_beforeResult.success) {
1682
- await connection.rollback();
1683
- throw new Error('onBeforeCompositeInsert failed: ' + _beforeResult.error);
1684
- }
1685
- }
1686
-
1687
- // Build header INSERT
1688
- const headerFields = [];
1689
- const headerValues = [];
1690
- const headerPlaceholders = [];
1691
- let idx = 1;
1692
- for (const [key, value] of Object.entries(headerData)) {
1693
- if (value !== undefined && value !== null) {
1694
- headerFields.push(key);
1695
- headerValues.push(value);
1696
- headerPlaceholders.push(':' + idx++);
1697
- }
1698
- }
1699
-
1700
- // Inject audit columns (created_at, created_by, updated_at, updated_by) via helper
1701
- idx = this._appendCreateAuditColumns(headerFields, headerValues, headerPlaceholders, headerData, eventContext, idx);
1702
-
1703
- const insertSql = 'INSERT INTO ' + this.writeSource + ' (' + headerFields.join(', ') + ') VALUES (' + headerPlaceholders.join(', ') + ')';
1704
- console.log('Executing header INSERT:', { query: insertSql, values: headerValues });
1705
- await connection.execute(insertSql, headerValues, { autoCommit: false });
1706
-
1707
- // SELECT back inserted header
1708
- const selectSql = 'SELECT * FROM ' + this.getTableSource('read') + ' WHERE ' + this.primaryKey + ' = :1';
1709
- const headerResult = await connection.execute(selectSql, [headerData[this.primaryKey]], { autoCommit: false, outFormat: oracledb.OUT_FORMAT_OBJECT });
1710
- const insertedHeader = this.formatResponseData(headerResult.rows[0]);
1711
- const masterPkValue = headerData[this.primaryKey];
1712
-
1713
- console.log('Header inserted successfully: ' + this.primaryKey + '=' + masterPkValue);
1714
-
1715
- // Insert detail items
1716
- const insertedItems = [];
1717
- const detailTableFull = '${detailTable}';
1718
- const fk = '${foreignKey}';
1719
- const detailPk = '${detailPrimaryKey}';
1720
- ${generatedFieldNames.length > 0 ? `const generatedFields = ${JSON.stringify(generatedFieldNames)};` : ''}
1721
-
1722
- for (const item of data[detailKey] || []) {
1723
- item[fk] = masterPkValue;
1724
-
1725
- // Auto-generate UUID untuk detail PK bila client tidak menyupply.
1726
- // Oracle belum memakai RETURNING INTO di generator, sehingga generator
1727
- // perlu mengetahui nilai PK di muka agar SELECT-back dapat me-retrieve
1728
- // row. Cocok untuk kolom VARCHAR2 yang PK-nya diisi trigger/DEFAULT
1729
- // UUID; untuk IDENTITY/SEQUENCE integer, client harus mengirim PK.
1730
- if (item[detailPk] === undefined || item[detailPk] === null) {
1731
- item[detailPk] = require('uuid').v7();
1732
- }
1733
- ${calcBlockCode ? '\n' + calcBlockCode + '\n' : ''}
1734
- const detailFields = [];
1735
- const detailValues = [];
1736
- const detailPlaceholders = [];
1737
- let dIdx = 1;
1738
-
1739
- for (const [key, value] of Object.entries(item)) {
1740
- ${generatedFieldNames.length > 0 ? `if (generatedFields.includes(key)) continue;` : ''}
1741
- if (value !== undefined && value !== null) {
1742
- detailFields.push(key);
1743
- detailValues.push(value);
1744
- detailPlaceholders.push(':' + dIdx++);
1745
- }
1746
- }
1747
-
1748
- // Inject audit columns ke detail INSERT via helper
1749
- dIdx = this._appendCreateAuditColumns(detailFields, detailValues, detailPlaceholders, item, eventContext, dIdx);
1750
-
1751
- const detailInsertSql = 'INSERT INTO ' + detailTableFull + ' (' + detailFields.join(', ') + ') VALUES (' + detailPlaceholders.join(', ') + ')';
1752
- console.log('Executing detail INSERT:', { query: detailInsertSql, values: detailValues });
1753
- await connection.execute(detailInsertSql, detailValues, { autoCommit: false });
1754
-
1755
- // SELECT back inserted detail
1756
- const detailSelectSql = 'SELECT * FROM ' + detailTableFull + ' WHERE ' + detailPk + ' = :1';
1757
- const detailResult = await connection.execute(detailSelectSql, [item[detailPk]], { autoCommit: false, outFormat: oracledb.OUT_FORMAT_OBJECT });
1758
- if (detailResult.rows[0]) {
1759
- insertedItems.push(detailResult.rows[0]);
1760
- }
1761
- }
1762
-
1763
- console.log('Inserted ' + insertedItems.length + ' detail item(s)');
1764
-
1765
- // --- Hook: onAfterCompositeInsert ---
1766
- if (eventContext && eventContext.componentEngine) {
1767
- var _ce2 = eventContext.componentEngine;
1768
- var _CB2 = eventContext.ContextBuilder;
1769
- var _afterCtx = _CB2.buildCompositeInsertAfterContext(headerData, insertedHeader, insertedItems, {
1770
- tableName: '${payload.tableName}',
1771
- detailTable: '${detailTable}',
1772
- foreignKey: '${foreignKey}',
1773
- primaryKey: this.primaryKey,
1774
- ...(eventContext.additionalContext || {})
1775
- });
1776
- var _afterResult = await _ce2.executeOnAfterComposite('insert', _afterCtx);
1777
- if (!_afterResult.success) {
1778
- await connection.rollback();
1779
- throw new Error('onAfterCompositeInsert failed: ' + _afterResult.error);
1780
- }
1781
- }
1782
-
1783
- await connection.commit();
1784
- console.log('Transaction committed successfully');
1785
-
1786
- // Invalidate cache setelah write operation berhasil
1787
- await this.invalidateCache();
1788
-
1789
- return {
1790
- ...insertedHeader,
1791
- [detailKey]: insertedItems
1792
- };
1793
- } catch (error) {
1794
- try { await connection.rollback(); } catch (e) { /* ignore */ }
1795
- console.error('Error in createComposite:', error);
1796
- throw error;
1797
- } finally {
1798
- try { await connection.close(); } catch (e) { /* ignore */ }
1799
- }
1800
- }
1801
- `;
1802
- }
1803
-
1804
- // updateComposite
1805
- if (actions.updateComposite) {
1806
- code += `
1807
- /**
1808
- * Composite update - Update header with granular detail operations (Oracle)
1809
- */
1810
- async updateComposite(data, eventContext = null) {
1811
- const connection = await db.getConnection();
1812
- try {
1813
- const primaryKeyValue = data[this.primaryKey];
1814
- if (!primaryKeyValue) {
1815
- throw new Error('Primary key ' + this.primaryKey + ' is required for update');
1816
- }
1817
-
1818
- // Check if record exists (also serves as prefetch oldData for hooks)
1819
- const checkSql = 'SELECT * FROM ' + this.writeSource + ' WHERE ' + this.primaryKey + ' = :1';
1820
- const checkResult = await connection.execute(checkSql, [primaryKeyValue], { autoCommit: false, outFormat: oracledb.OUT_FORMAT_OBJECT });
1821
- if (!checkResult.rows || checkResult.rows.length === 0) {
1822
- throw new Error('Record not found');
1823
- }
1824
-
1825
- const oldData = this.formatResponseData(checkResult.rows[0]);
1826
-
1827
- // Extract header data
1828
- const headerData = { ...data };
1829
- const detailKey = '${detailTableName}';
1830
- delete headerData[detailKey];
1831
- delete headerData[this.primaryKey];
1832
-
1833
- // --- Hook: onBeforeCompositeUpdate ---
1834
- if (eventContext && eventContext.componentEngine) {
1835
- var _ce = eventContext.componentEngine;
1836
- var _CB = eventContext.ContextBuilder;
1837
- var _detailOps = data[detailKey] || {};
1838
- var _beforeCtx = _CB.buildCompositeUpdateBeforeContext(headerData, oldData, {
1839
- insert: _detailOps.insert || [],
1840
- update: _detailOps.update || [],
1841
- delete: _detailOps.delete || []
1842
- }, {
1843
- tableName: '${payload.tableName}',
1844
- detailTable: '${detailTable}',
1845
- foreignKey: '${foreignKey}',
1846
- primaryKey: this.primaryKey,
1847
- ...(eventContext.additionalContext || {})
1848
- });
1849
- var _beforeResult = await _ce.executeOnBeforeComposite('update', _beforeCtx);
1850
- if (!_beforeResult.success) {
1851
- await connection.rollback();
1852
- throw new Error('onBeforeCompositeUpdate failed: ' + _beforeResult.error);
1853
- }
1854
- }
1855
-
1856
- // Build header UPDATE
1857
- const headerFields = [];
1858
- const headerValues = [];
1859
- let idx = 1;
1860
- for (const [key, value] of Object.entries(headerData)) {
1861
- if (value !== undefined && value !== null) {
1862
- headerFields.push(key + ' = :' + idx++);
1863
- headerValues.push(value);
1864
- }
1865
- }
1866
-
1867
- // Inject audit columns (updated_at, updated_by) via helper
1868
- idx = this._appendUpdateAuditColumns(headerFields, headerValues, headerData, eventContext, idx);
1869
-
1870
- headerValues.push(primaryKeyValue);
1871
- const updateSql = 'UPDATE ' + this.writeSource + ' SET ' + headerFields.join(', ') + ' WHERE ' + this.primaryKey + ' = :' + idx;
1872
- console.log('Executing header UPDATE:', { query: updateSql, values: headerValues });
1873
- await connection.execute(updateSql, headerValues, { autoCommit: false });
1874
-
1875
- // SELECT back updated header
1876
- const selectSql = 'SELECT * FROM ' + this.getTableSource('read') + ' WHERE ' + this.primaryKey + ' = :1';
1877
- const headerResult = await connection.execute(selectSql, [primaryKeyValue], { autoCommit: false, outFormat: oracledb.OUT_FORMAT_OBJECT });
1878
- let updatedHeader = this.formatResponseData(headerResult.rows[0]);
1879
-
1880
- // Process detail items
1881
- const detailTableFull = '${detailTable}';
1882
- const fk = '${foreignKey}';
1883
- const detailPk = '${detailPrimaryKey}';
1884
- ${generatedFieldNames.length > 0 ? `const generatedFields = ${JSON.stringify(generatedFieldNames)};` : ''}
1885
-
1886
- const detailOperations = data[detailKey] || {};
1887
- const { insert: insertItems = [], update: updateItems = [], delete: deleteItems = [] } = detailOperations;
1888
-
1889
- const deletedItems = [];
1890
- const updatedItems = [];
1891
- const insertedItems = [];
1892
-
1893
- // 1. DELETE operations
1894
- for (const item of deleteItems) {
1895
- if (!item[detailPk]) throw new Error('Missing ' + detailPk + ' in delete operation');
1896
- const delSql = 'DELETE FROM ' + detailTableFull + ' WHERE ' + detailPk + ' = :1';
1897
- await connection.execute(delSql, [item[detailPk]], { autoCommit: false });
1898
- deletedItems.push(item);
1899
- }
1900
- console.log('Deleted ' + deletedItems.length + ' detail item(s)');
1901
-
1902
- // 2. UPDATE operations
1903
- for (const item of updateItems) {
1904
- if (!item[detailPk]) throw new Error('Missing ' + detailPk + ' in update operation');
1905
- ${calcBlockCode ? '\n' + calcBlockCode + '\n' : ''}
1906
- const dFields = [];
1907
- const dValues = [];
1908
- let dIdx = 1;
1909
- for (const [key, value] of Object.entries(item)) {
1910
- if (key === detailPk) continue;
1911
- ${generatedFieldNames.length > 0 ? `if (generatedFields.includes(key)) continue;` : ''}
1912
- if (value !== undefined && value !== null) {
1913
- dFields.push(key + ' = :' + dIdx++);
1914
- dValues.push(value);
1915
- }
1916
- }
1917
-
1918
- // Inject audit columns (updated_at, updated_by) ke detail UPDATE via helper
1919
- dIdx = this._appendUpdateAuditColumns(dFields, dValues, item, eventContext, dIdx);
1920
-
1921
- dValues.push(item[detailPk]);
1922
- const dUpdateSql = 'UPDATE ' + detailTableFull + ' SET ' + dFields.join(', ') + ' WHERE ' + detailPk + ' = :' + dIdx;
1923
- await connection.execute(dUpdateSql, dValues, { autoCommit: false });
1924
- updatedItems.push(item);
1925
- }
1926
- console.log('Updated ' + updatedItems.length + ' detail item(s)');
1927
-
1928
- // 3. INSERT operations
1929
- for (const item of insertItems) {
1930
- item[fk] = primaryKeyValue;
1931
-
1932
- // Auto-generate UUID untuk detail PK bila client tidak menyupply
1933
- // (konsisten dengan createComposite Oracle)
1934
- if (item[detailPk] === undefined || item[detailPk] === null) {
1935
- item[detailPk] = require('uuid').v7();
1936
- }
1937
- ${calcBlockCode ? '\n' + calcBlockCode + '\n' : ''}
1938
- const dFields = [];
1939
- const dValues = [];
1940
- const dPlaceholders = [];
1941
- let dIdx = 1;
1942
- for (const [key, value] of Object.entries(item)) {
1943
- ${generatedFieldNames.length > 0 ? `if (generatedFields.includes(key)) continue;` : ''}
1944
- if (value !== undefined && value !== null) {
1945
- dFields.push(key);
1946
- dValues.push(value);
1947
- dPlaceholders.push(':' + dIdx++);
1948
- }
1949
- }
1950
-
1951
- // Inject audit columns ke detail INSERT via helper
1952
- dIdx = this._appendCreateAuditColumns(dFields, dValues, dPlaceholders, item, eventContext, dIdx);
1953
-
1954
- const dInsertSql = 'INSERT INTO ' + detailTableFull + ' (' + dFields.join(', ') + ') VALUES (' + dPlaceholders.join(', ') + ')';
1955
- await connection.execute(dInsertSql, dValues, { autoCommit: false });
1956
- insertedItems.push(item);
1957
- }
1958
- console.log('Inserted ' + insertedItems.length + ' new detail item(s)');
1959
-
1960
- // Get all current detail items
1961
- const allItemsSql = 'SELECT * FROM ' + detailTableFull + ' WHERE ' + fk + ' = :1 ORDER BY line_number';
1962
- const allItemsResult = await connection.execute(allItemsSql, [primaryKeyValue], { autoCommit: false, outFormat: oracledb.OUT_FORMAT_OBJECT });
1963
- const allItems = allItemsResult.rows || [];
1964
- ${headerCalculations ? `
1965
- // Recalculate header totals
1966
- const calculations = ${JSON.stringify(headerCalculations)};
1967
- const recalcFields = [];
1968
- const recalcValues = [];
1969
- let recalcIdx = 1;
1970
-
1971
- if (calculations.total_items) {
1972
- recalcFields.push('total_items = :' + recalcIdx++);
1973
- recalcValues.push(allItems.length);
1974
- }
1975
- if (calculations.total_qty && calculations.total_qty.source) {
1976
- const qtyField = calculations.total_qty.source.replace('items.', '').toUpperCase();
1977
- const totalQty = allItems.reduce(function(sum, item) { return sum + (Number(item[qtyField]) || 0); }, 0);
1978
- recalcFields.push('total_qty = :' + recalcIdx++);
1979
- recalcValues.push(totalQty);
1980
- }
1981
- ${amountQtyField ? `if (calculations.total_amount) {
1982
- const totalAmount = allItems.reduce(function(sum, item) {
1983
- var qty = Number(item.${amountQtyField.toUpperCase()} || item.${amountQtyField}) || 0;
1984
- var price = Number(item.${amountPriceField.toUpperCase()} || item.${amountPriceField}) || 0;
1985
- return sum + (qty * price);
1986
- }, 0);
1987
- recalcFields.push('total_amount = :' + recalcIdx++);
1988
- recalcValues.push(totalAmount);
1989
- }` : '// WARNING: headerCalculations.total_amount skipped — no qty field configured'}
1990
-
1991
- if (recalcFields.length > 0) {
1992
- // Inject audit columns (updated_at, updated_by) ke recalc UPDATE via helper
1993
- recalcIdx = this._appendUpdateAuditColumns(recalcFields, recalcValues, data, eventContext, recalcIdx);
1994
- recalcValues.push(primaryKeyValue);
1995
- var recalcSql = 'UPDATE ' + this.writeSource + ' SET ' + recalcFields.join(', ') + ' WHERE ' + this.primaryKey + ' = :' + recalcIdx;
1996
- console.log('Recalculating header totals:', { query: recalcSql, values: recalcValues });
1997
- await connection.execute(recalcSql, recalcValues, { autoCommit: false });
1998
-
1999
- var recalcResult = await connection.execute(selectSql, [primaryKeyValue], { autoCommit: false, outFormat: oracledb.OUT_FORMAT_OBJECT });
2000
- updatedHeader = this.formatResponseData(recalcResult.rows[0]);
2001
- }
2002
- ` : ''}
2003
- // --- Hook: onAfterCompositeUpdate ---
2004
- if (eventContext && eventContext.componentEngine) {
2005
- var _ce2 = eventContext.componentEngine;
2006
- var _CB2 = eventContext.ContextBuilder;
2007
- var _afterCtx = _CB2.buildCompositeUpdateAfterContext(headerData, oldData, updatedHeader, {
2008
- inserted: insertedItems,
2009
- updated: updatedItems,
2010
- deleted: deletedItems
2011
- }, {
2012
- tableName: '${payload.tableName}',
2013
- detailTable: '${detailTable}',
2014
- foreignKey: '${foreignKey}',
2015
- primaryKey: this.primaryKey,
2016
- ...(eventContext.additionalContext || {})
2017
- });
2018
- var _afterResult = await _ce2.executeOnAfterComposite('update', _afterCtx);
2019
- if (!_afterResult.success) {
2020
- await connection.rollback();
2021
- throw new Error('onAfterCompositeUpdate failed: ' + _afterResult.error);
2022
- }
2023
- }
2024
-
2025
- await connection.commit();
2026
- console.log('Transaction committed successfully');
2027
-
2028
- // Invalidate cache setelah write operation berhasil
2029
- await this.invalidateCache();
2030
-
2031
- return {
2032
- ...updatedHeader,
2033
- [detailKey]: allItems,
2034
- _operations: {
2035
- deleted: deletedItems.length,
2036
- updated: updatedItems.length,
2037
- inserted: insertedItems.length
2038
- }
2039
- };
2040
- } catch (error) {
2041
- try { await connection.rollback(); } catch (e) { /* ignore */ }
2042
- console.error('Error in updateComposite:', error);
2043
- throw error;
2044
- } finally {
2045
- try { await connection.close(); } catch (e) { /* ignore */ }
2046
- }
2047
- }
2048
- `;
2049
- }
2050
-
2051
- // readComposite
2052
- if (actions.readComposite) {
2053
- // Build detail query loader code yang support prefix `file:` maupun SQL literal
2054
- let oracleDetailLoaderCode;
2055
- if (detailQuery && typeof detailQuery === 'string' && detailQuery.startsWith('file:')) {
2056
- const relativePath = detailQuery.replace('file:', '');
2057
- const fileName = relativePath.split('/').pop();
2058
- oracleDetailLoaderCode = `
2059
- // Load detail query dari file lokal
2060
- let detailSql;
2061
- try {
2062
- const detailQueryFilePath = path.join(__dirname, 'query', '${fileName}');
2063
- if (fs.existsSync(detailQueryFilePath)) {
2064
- detailSql = fs.readFileSync(detailQueryFilePath, 'utf8').trim();
2065
- } else {
2066
- throw new Error(\`Detail query file not found: \${detailQueryFilePath}\`);
2067
- }
2068
- } catch (error) {
2069
- throw new Error('Failed to load detail query file: ' + error.message);
2070
- }`;
2071
- } else if (detailQuery) {
2072
- oracleDetailLoaderCode = `const detailSql = \`${detailQuery.replace(/\$1/g, ':1')}\`;`;
2073
- } else {
2074
- oracleDetailLoaderCode = `const detailSql = 'SELECT * FROM ${detailTable} WHERE ${foreignKey} = :1 ORDER BY line_number';`;
2075
- }
2076
-
2077
- code += `
2078
- /**
2079
- * Composite read - Read header with detail items (Oracle)
2080
- */
2081
- async readComposite(options) {
2082
- try {
2083
- if (!options.where) {
2084
- throw new Error('Invalid request format: where parameter is required');
2085
- }
2086
-
2087
- let whereClauseResult;
2088
- try {
2089
- whereClauseResult = this.buildComplexWhereClause(options.where);
2090
- } catch (e) {
2091
- const error = new Error('Invalid where conditions: ' + e.message);
2092
- error.statusCode = 400;
2093
- throw error;
2094
- }
2095
- const { sql: whereClause, params } = whereClauseResult;
2096
- const headerSql = 'SELECT * FROM ' + this.getTableSource('read') + ' WHERE ' + whereClause;
2097
- const headerResults = await db.executeQuery(headerSql, params);
2098
-
2099
- if (!headerResults || headerResults.length === 0) {
2100
- return { success: true, count: 0, data: [] };
2101
- }
2102
-
2103
- const compositeResults = [];
2104
- const detailKey = '${detailTableName}';
2105
- ${oracleDetailLoaderCode}
2106
-
2107
- for (const header of headerResults) {
2108
- const formattedHeader = this.formatResponseData(header);
2109
- const pkValue = formattedHeader[this.primaryKey] || header[this.primaryKey.toUpperCase()];
2110
- const detailResults = await db.executeQuery(detailSql, [pkValue]);
2111
- compositeResults.push({
2112
- ...formattedHeader,
2113
- [detailKey]: detailResults || []
2114
- });
2115
- }
2116
-
2117
- return {
2118
- success: true,
2119
- count: compositeResults.length,
2120
- data: compositeResults
2121
- };
2122
- } catch (error) {
2123
- console.error('Error in readComposite:', error);
2124
- throw error;
2125
- }
2126
- }
2127
- `;
2128
- }
2129
-
2130
- return code;
2131
- })()}
2132
- }
2133
-
2134
- module.exports = new ${className}Model();`;
2135
- }
2136
-
2137
- /**
2138
- * Membuat template untuk submodule Oracle
2139
- * @param {string} moduleName - Nama module
2140
- * @param {string} endpointName - Nama endpoint
2141
- * @param {Object} payload - Data payload dari JSON
2142
- * @returns {string} Template submodule untuk Oracle
2143
- */
2144
- function createOracleSubmoduleTemplate(moduleName, endpointName, payload) {
2145
- const modelVarName = toCamelCase(endpointName) + 'Model';
2146
- const primaryKey = payload.primaryKey || 'id';
2147
- const timestamp = new Date().toISOString();
2148
- const enabledActions = Object.entries(payload.action || {})
2149
- .filter(([, enabled]) => enabled)
2150
- .map(([action]) => action);
2151
- const hasComponents = payload.components && Array.isArray(payload.components) && payload.components.length > 0;
2152
-
2153
- // Compute componentConfig values untuk generated submodule (parsed oleh config-extractor di runtime)
2154
- const exportQuery = payload.exportQuery || `SELECT ${payload.fieldName.join(', ')} FROM ${payload.tableName}`;
2155
- const exportQueryEscaped = exportQuery.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
2156
- const fieldNameStr = JSON.stringify(payload.fieldName);
2157
- const columnFormatsStr = payload.columnFormats ? JSON.stringify(payload.columnFormats) : 'null';
2158
- const fieldLabelsStr = payload.fieldLabels ? JSON.stringify(payload.fieldLabels) : 'null';
2159
- const importConfigObj = payload.action && payload.action.import ? {
2160
- enabled: true,
2161
- upsertKeys: (payload.importConfig || {}).upsertKeys || [primaryKey],
2162
- upsertStrategy: (payload.importConfig || {}).upsertStrategy || 'update_existing',
2163
- requiredFields: (payload.importConfig || {}).requiredFields || [],
2164
- validations: (payload.importConfig || {}).validations || {},
2165
- lookupFields: (payload.importConfig || {}).lookupFields || {},
2166
- excludeFromImport: (payload.importConfig || {}).excludeFromImport || [],
2167
- chunkSize: (payload.importConfig || {}).chunkSize || 100
2168
- } : null;
2169
- const importConfigStr = importConfigObj ? JSON.stringify(importConfigObj) : 'null';
2170
- const exportQueryStr = payload.action && payload.action.export ? `'${exportQueryEscaped}'` : 'null';
2171
- const adjustConfigObj = payload.action && payload.action.adjust ? (payload.adjustConfig || {}) : null;
2172
- const adjustConfigStr = adjustConfigObj ? JSON.stringify(adjustConfigObj) : 'null';
2173
-
2174
- // Layer 2 (Concurrency by Design): inject fieldPolicy sebagai argumen
2175
- // tambahan ke updateData()/adjustData(). Untuk payload TANPA fieldPolicy,
2176
- // policyArg = '' sehingga output identik dengan baseline path relaxed.
2177
- const policyArg = payload.fieldPolicy
2178
- ? `, ${JSON.stringify(payload.fieldPolicy)}`
2179
- : '';
2180
-
2181
- // Component engine declarations: selalu emit `let`-binding sehingga composite
2182
- // handler (yang selalu mereferensi componentEngine/ContextBuilder untuk
2183
- // membangun eventContext) tidak throw ReferenceError saat payload tidak
2184
- // memiliki components. Runtime require hanya dilakukan bila hasComponents.
2185
- let additionalRequires = 'let componentEngine = null;\nlet ContextBuilder = null;\n';
2186
- if (hasComponents) {
2187
- additionalRequires += `componentEngine = require('@restforgejs/platform/src/utils/component-engine').componentEngine;\n`;
2188
- additionalRequires += `ContextBuilder = require('@restforgejs/platform/src/utils/context-builder');\n`;
2189
- }
2190
-
2191
- // Request scope ownership verification snippets (Layer 1 RLS).
2192
- // Three variants are needed across the inline endpoint handlers:
2193
- // - standard: fetch by PK + scope, 404 on mismatch (/update, /adjust)
2194
- // - postFetch: compare after getData in JS (/first; where is {key,value} object)
2195
- // - master: composite master ownership with specific "Master record" message
2196
- // Each yields an empty string when payload.requestScope is absent.
2197
- const requestScopeOwnershipCheckStandard = payload.requestScope ? `
2198
- // Request scope ownership verification (Layer 1 RLS)
2199
- if (req._requestScope) {
2200
- const scopeCheck = await ${modelVarName}.getData({
2201
- where: [
2202
- { key: primaryKey, value: req.body[primaryKey] },
2203
- { key: req._requestScope.column, value: req._requestScope.value }
2204
- ]
2205
- });
2206
- if (!scopeCheck.success || !scopeCheck.data || scopeCheck.data.length === 0) {
2207
- return res.status(404).json({
2208
- success: false,
2209
- error: 'Data not found',
2210
- message: '${endpointName} data not found or access denied',
2211
- timestamp: new Date().toISOString()
2212
- });
2213
- }
2214
- }
2215
- ` : '';
2216
-
2217
- const requestScopeOwnershipCheckPostFetch = payload.requestScope ? `
2218
- // Request scope ownership verification (Layer 1 RLS)
2219
- if (req._requestScope && result.data && result.data.length > 0) {
2220
- const record = result.data[0];
2221
- if (record[req._requestScope.column] !== undefined &&
2222
- String(record[req._requestScope.column]) !== String(req._requestScope.value)) {
2223
- return res.status(404).json({
2224
- success: false,
2225
- error: 'Data not found',
2226
- message: '${endpointName} data not found',
2227
- timestamp: new Date().toISOString()
2228
- });
2229
- }
2230
- }
2231
- ` : '';
2232
-
2233
- const requestScopeOwnershipCheckMaster = payload.requestScope ? `
2234
- // Request scope ownership verification (Layer 1 RLS) — master record
2235
- if (req._requestScope) {
2236
- const scopeCheck = await ${modelVarName}.getData({
2237
- where: [
2238
- { key: '${primaryKey}', value: data.${primaryKey} },
2239
- { key: req._requestScope.column, value: req._requestScope.value }
2240
- ]
2241
- });
2242
- if (!scopeCheck.success || !scopeCheck.data || scopeCheck.data.length === 0) {
2243
- return res.status(404).json({
2244
- success: false,
2245
- error: 'Data not found',
2246
- message: 'Master record not found or access denied',
2247
- timestamp: new Date().toISOString()
2248
- });
2249
- }
2250
- }
2251
- ` : '';
2252
-
2253
- let subModuleContent = `const express = require('express');
2254
- const router = express.Router();
2255
- const ${modelVarName} = require('../../models/${moduleName}/${endpointName}');
2256
- ${additionalRequires}
2257
- /**
2258
- * ${toPascalCase(endpointName)} Submodule - Oracle Database
2259
- * Generated: ${timestamp}
2260
- *
2261
- * Oracle-optimized endpoints untuk ${endpointName}
2262
- * Actions: ${enabledActions.join(', ')}
2263
- * Table: ${payload.tableName}
2264
- * Database: Oracle
2265
- */
2266
-
2267
- // Primary key untuk endpoint ini
2268
- const primaryKey = '${primaryKey}';
2269
-
2270
- // Component configuration untuk export/import (parsed oleh config-extractor — jangan dimodifikasi)
2271
- const componentConfig = {
2272
- tableName: '${payload.tableName}',
2273
- fieldName: ${fieldNameStr},
2274
- exportQuery: ${exportQueryStr},
2275
- columnFormats: ${columnFormatsStr},
2276
- fieldLabels: ${fieldLabelsStr},
2277
- importConfig: ${importConfigStr},
2278
- adjustConfig: ${adjustConfigStr},
2279
- uploadConfig: ${payload.uploadConfig ? JSON.stringify(payload.uploadConfig) : 'null'},
2280
- requestScope: ${payload.requestScope ? JSON.stringify(payload.requestScope) : 'null'}
2281
- };
2282
- ${hasComponents ? `
2283
- // Initialize component engine dengan event handlers dari payload
2284
- const _componentPayload = { components: ${JSON.stringify(payload.components)} };
2285
- componentEngine.loadConfigurationFromObject(_componentPayload).then(result => {
2286
- if (result.success) {
2287
- console.log(\`Component configuration loaded for ${moduleName}/${endpointName}: \${result.componentsLoaded} components\`);
2288
- }
2289
- }).catch(err => {
2290
- console.error(\`Failed to load component configuration for ${moduleName}/${endpointName}:\`, err.message);
2291
- });
2292
- ` : ''}
2293
- // CORS ditangani di level app oleh cors middleware (lihat konfigurasi CORS_ENABLED dan CORS_ORIGINS di .env)
2294
-
2295
- // Request ID untuk tracing — support correlation ID dari upstream
2296
- router.use((req, res, next) => {
2297
- req.oraRequestId = req.headers['x-correlation-id'] || \`ora_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
2298
- res.setHeader('X-Correlation-ID', req.oraRequestId);
2299
- res.setHeader('X-ORA-Request-ID', req.oraRequestId);
2300
- next();
2301
- });
2302
-
2303
-
2304
-
2305
- // Middleware untuk validasi payload Oracle
2306
- router.use((req, res, next) => {
2307
- if (req.method === 'POST') {
2308
- // Skip validation untuk endpoint export/import (ditangani oleh centralized handler)
2309
- if (req.path.startsWith('/import') || req.path.startsWith('/export')) {
2310
- return next();
2311
- }
2312
- try {
2313
- if (!req.body || Object.keys(req.body).length === 0) {
2314
- return res.status(400).json({
2315
- success: false,
2316
- error: 'Missing payload',
2317
- message: 'Payload cannot be empty',
2318
- timestamp: new Date().toISOString()
2319
- });
2320
- }
2321
-
2322
- const endpoint = req.path.substring(1);
2323
-
2324
- if (endpoint === 'first') {
2325
- if (Array.isArray(req.body.where) && req.body.where.length === 1) {
2326
- req.body.where = req.body.where[0];
2327
- }
2328
- if (!req.body.where || typeof req.body.where !== 'object' || Array.isArray(req.body.where)) {
2329
- return res.status(400).json({
2330
- success: false,
2331
- error: 'Invalid payload',
2332
- message: 'Where must be a single condition {key, value}',
2333
- example: {
2334
- "where": { "key": "field_name", "value": "field_value" },
2335
- "select": ["field1", "field2"]
2336
- },
2337
- timestamp: new Date().toISOString()
2338
- });
2339
- }
2340
- if (req.body.where.conditions || req.body.where.logic) {
2341
- return res.status(400).json({
2342
- success: false,
2343
- error: 'Invalid payload',
2344
- message: 'Advanced where format is not supported in /first endpoint. Use /read endpoint for complex queries',
2345
- example: {
2346
- "where": { "key": "field_name", "value": "field_value" }
2347
- },
2348
- timestamp: new Date().toISOString()
2349
- });
2350
- }
2351
- }
2352
-
2353
- if (endpoint === 'delete' && (!req.body.where)) {
2354
- return res.status(400).json({
2355
- success: false,
2356
- error: 'Invalid payload',
2357
- message: 'DELETE payload must include a where property',
2358
- example: {
2359
- "where": [{ "key": "${primaryKey}", "value": "your-value" }]
2360
- },
2361
- timestamp: new Date().toISOString()
2362
- });
2363
- }
2364
- } catch (error) {
2365
- console.error(\`Error validating Oracle payload for \${req.path}:\`, error);
2366
- return res.status(400).json({
2367
- success: false,
2368
- error: 'Invalid payload',
2369
- message: 'Invalid payload format',
2370
- details: error.message,
2371
- timestamp: new Date().toISOString()
2372
- });
2373
- }
2374
- }
2375
- next();
2376
- });
2377
-
2378
- `;
2379
-
2380
- // ─── Request Scope Middleware (Layer 1 RLS) ──────────────────
2381
- // Inject after validation middleware, before endpoint handlers.
2382
- // Empty string when payload.requestScope is absent (backward compat).
2383
- subModuleContent += buildRequestScopeMiddleware(payload, endpointName);
2384
-
2385
- // POST /datatables
2386
- if (payload.action && payload.action.datatables) {
2387
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/datatables - Data untuk DataTables
2388
- router.post('/datatables', async (req, res) => {
2389
- try {
2390
- const options = {
2391
- searchValue: req.body.search?.value || req.body.searchValue || req.body.search_value || '',
2392
- searchBy: req.body.searchBy || req.body.search_by || 'all',
2393
- perPage: Math.min(parseInt(req.body.length || req.body.pagination?.perpage || 10, 10), 1000),
2394
- start: Math.max(parseInt(req.body.start || 0, 10), 0),
2395
- draw: req.body.draw || '1'
2396
- };
2397
-
2398
- // Handle sort_columns
2399
- if (req.body.sort_columns && Array.isArray(req.body.sort_columns) && req.body.sort_columns.length > 0) {
2400
- options.sort_columns = req.body.sort_columns.map(item => ({
2401
- column: item.column,
2402
- direction: (item.direction || 'ASC').toUpperCase()
2403
- }));
2404
- }
2405
-
2406
- // Fallback: Handle DataTables standard format (order[0][column] dan order[0][dir])
2407
- if (req.body['order[0][column]'] !== undefined) {
2408
- options['order[0][column]'] = req.body['order[0][column]'];
2409
- }
2410
- if (req.body['order[0][dir]'] !== undefined) {
2411
- options['order[0][dir]'] = req.body['order[0][dir]'];
2412
- }
2413
-
2414
- // Handle filters dengan sanitasi
2415
- if (req.body.filters && typeof req.body.filters === 'object') {
2416
- const sanitizedFilters = {};
2417
- for (const [key, value] of Object.entries(req.body.filters)) {
2418
- if (value !== null && value !== undefined && value !== '' && value !== 'all' && value !== '-') {
2419
- sanitizedFilters[key] = value;
2420
- }
2421
- }
2422
- if (Object.keys(sanitizedFilters).length > 0) {
2423
- options.filters = sanitizedFilters;
2424
- }
2425
- }
2426
-
2427
- // Support WHERE conditions
2428
- if (req.body.where) {
2429
- options.where = req.body.where;
2430
- }
2431
-
2432
- // Advanced filters support
2433
- if (req.body.advanced_filters && Array.isArray(req.body.advanced_filters)) {
2434
- options.advancedFilters = req.body.advanced_filters;
2435
- }
2436
-
2437
- // Gunakan model untuk mendapatkan data
2438
- const result = await ${modelVarName}.getDatatables(options);
2439
-
2440
- // Menambahkan nomor baris untuk DataTables
2441
- if (result.data && Array.isArray(result.data)) {
2442
- result.data = result.data.map((item, index) => ({
2443
- ...item,
2444
- rownumerator: options.start + index + 1
2445
- }));
2446
- }
2447
-
2448
- return res.json(result);
2449
- } catch (error) {
2450
- console.error('Error in ${endpointName} datatables:', error);
2451
- const statusCode = error.statusCode || 500;
2452
- return res.status(statusCode).json({
2453
- success: false,
2454
- error: statusCode === 400 ? 'Bad Request' : 'Internal Server Error',
2455
- message: statusCode === 400 ? error.message : 'An error occurred while fetching ${endpointName} data',
2456
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2457
- timestamp: new Date().toISOString()
2458
- });
2459
- }
2460
- });
2461
-
2462
- `;
2463
- }
2464
-
2465
- // GET /lookup - Dynamic
2466
- if (payload.action && payload.action.lookup) {
2467
- subModuleContent += `// GET /api/${moduleName}/${endpointName}/lookup - Oracle Dynamic Lookup
2468
- router.get('/lookup', async (req, res) => {
2469
- const oraRequestId = req.oraRequestId;
2470
-
2471
- try {
2472
- const requestMode = req.headers['x-request-mode'];
2473
-
2474
- if (requestMode !== 'dynamic') {
2475
- return res.status(400).json({
2476
- success: false,
2477
- error: 'Invalid Request Mode',
2478
- message: 'X-Request-Mode header must be set to dynamic',
2479
- timestamp: new Date().toISOString()
2480
- });
2481
- }
2482
-
2483
- let search = req.query.search || '';
2484
- if (Array.isArray(search)) {
2485
- search = search[0] || '';
2486
- }
2487
-
2488
- // Search length validation
2489
- if (search.length > 100) {
2490
- return res.status(400).json({
2491
- success: false,
2492
- error: 'Search Too Long',
2493
- message: 'Search parameter must not exceed 100 characters',
2494
- timestamp: new Date().toISOString()
2495
- });
2496
- }
2497
-
2498
- console.log(\`[ORA-LKP] \${oraRequestId} dynamic search: \${search}\`);
2499
-
2500
- // Collect extra filters dari query params
2501
- const extraFilters = {};
2502
- for (const [key, value] of Object.entries(req.query)) {
2503
- if (key !== 'search' && ${modelVarName}.validFields.includes(key) && value) {
2504
- extraFilters[key] = value;
2505
- }
2506
- }
2507
-
2508
- const startTime = Date.now();
2509
- const list = Object.keys(extraFilters).length > 0 ?
2510
- await ${modelVarName}.getLookupDataDynamic(search, extraFilters) :
2511
- await ${modelVarName}.getLookupData(search);
2512
- const lookupTime = Date.now() - startTime;
2513
-
2514
- console.log(\`[ORA-LKP] \${oraRequestId} found \${list.length} results in \${lookupTime}ms\`);
2515
-
2516
- return res.json({
2517
- success: true,
2518
- count: list.length,
2519
- data: list,
2520
- search: search,
2521
- _oracle: { requestId: oraRequestId, queryTime: lookupTime, timestamp: new Date().toISOString() }
2522
- });
2523
- } catch (error) {
2524
- console.error(\`[ORA-LKP] Error \${oraRequestId}:\`, error);
2525
- return res.status(500).json({
2526
- success: false,
2527
- error: 'Internal Server Error',
2528
- message: 'An error occurred while looking up ${endpointName} data',
2529
- details: error.message,
2530
- timestamp: new Date().toISOString()
2531
- });
2532
- }
2533
- });
2534
-
2535
- `;
2536
- }
2537
-
2538
- // POST /lookup - Static
2539
- if (payload.action && payload.action.lookup) {
2540
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/lookup - Oracle Static Lookup
2541
- router.post('/lookup', async (req, res) => {
2542
- const oraRequestId = req.oraRequestId;
2543
-
2544
- try {
2545
- const requestMode = req.headers['x-request-mode'];
2546
-
2547
- if (requestMode !== 'static') {
2548
- return res.status(400).json({
2549
- success: false,
2550
- error: 'Invalid Request Mode',
2551
- message: 'X-Request-Mode header must be set to static for POST lookup',
2552
- timestamp: new Date().toISOString()
2553
- });
2554
- }
2555
-
2556
- console.log(\`[ORA-LKP] \${oraRequestId} static lookup:\`, JSON.stringify(req.body, null, 2));
2557
-
2558
- const startTime = Date.now();
2559
- let list;
2560
-
2561
- if (req.body.where) {
2562
- // New format dengan where clause + optional select dan order
2563
- list = await ${modelVarName}.getLookupDataWithFilter(req.body);
2564
- } else {
2565
- // Legacy format dengan selected_tag
2566
- const selectedTag = req.body.selected_tag || '';
2567
- list = await ${modelVarName}.getStaticLookupData(selectedTag);
2568
- }
2569
-
2570
- const lookupTime = Date.now() - startTime;
2571
- console.log(\`[ORA-LKP] \${oraRequestId} found \${list.length} results in \${lookupTime}ms\`);
2572
-
2573
- return res.json({
2574
- success: true,
2575
- count: list.length,
2576
- data: list,
2577
- _oracle: { requestId: oraRequestId, queryTime: lookupTime, timestamp: new Date().toISOString() }
2578
- });
2579
- } catch (error) {
2580
- console.error(\`[ORA-LKP] Error \${oraRequestId}:\`, error);
2581
- return res.status(500).json({
2582
- success: false,
2583
- error: 'Internal Server Error',
2584
- message: 'An error occurred while looking up ${endpointName} data',
2585
- details: error.message,
2586
- timestamp: new Date().toISOString()
2587
- });
2588
- }
2589
- });
2590
-
2591
- `;
2592
- }
2593
-
2594
- // POST /create
2595
- if (payload.action && payload.action.create) {
2596
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/create - Oracle Insert
2597
- router.post('/create', async (req, res) => {
2598
- try {
2599
- if (!req.body || Object.keys(req.body).length === 0) {
2600
- return res.status(400).json({
2601
- success: false,
2602
- error: 'Invalid payload',
2603
- message: 'Payload cannot be empty',
2604
- timestamp: new Date().toISOString()
2605
- });
2606
- }
2607
-
2608
- // Validasi data
2609
- if (typeof ${modelVarName}.validateData === 'function') {
2610
- const validation = await ${modelVarName}.validateData(req.body, 'insert');
2611
- if (!validation.isValid) {
2612
- return res.status(400).json({
2613
- success: false,
2614
- error: 'Validation failed',
2615
- message: 'Invalid data',
2616
- errors: validation.errors,
2617
- timestamp: new Date().toISOString()
2618
- });
2619
- }
2620
- req.body = { ...req.body, ...validation.sanitizedData };
2621
- }
2622
-
2623
- ${hasComponents ? `
2624
- // Component engine: build eventContext untuk model-level event lifecycle
2625
- if (componentEngine && ContextBuilder) {
2626
- try {
2627
- const eventContext = {
2628
- componentEngine: componentEngine,
2629
- ContextBuilder: ContextBuilder,
2630
- tableName: '${payload.tableName}',
2631
- additionalContext: {
2632
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
2633
- options: req.bodyOptions || {},
2634
- requestId: req.id || null
2635
- }
2636
- };
2637
- var result = await ${modelVarName}.addData(req.body, eventContext);
2638
- console.log('[INTEGRATED TRANSACTION] INSERT completed successfully with events');
2639
- } catch (error) {
2640
- console.error('[INTEGRATED TRANSACTION] INSERT failed:', error.message);
2641
- throw error;
2642
- }
2643
- } else {
2644
- try {
2645
- var result = await ${modelVarName}.addData(req.body, { additionalContext: { requestId: req.id || null } });
2646
- console.log('[FALLBACK] INSERT completed without events');
2647
- } catch (error) {
2648
- console.error('[FALLBACK] INSERT failed:', error.message);
2649
- throw error;
2650
- }
2651
- }
2652
- ` : `
2653
- try {
2654
- var result = await ${modelVarName}.addData(req.body, { additionalContext: { requestId: req.id || null } });
2655
- console.log('[FALLBACK] INSERT completed without events');
2656
- } catch (error) {
2657
- console.error('[FALLBACK] INSERT failed:', error.message);
2658
- throw error;
2659
- }
2660
- `}
2661
- console.log(\`${endpointName} data added successfully: \${result.${primaryKey} || 'new record'}\`);
2662
-
2663
- return res.status(201).json({
2664
- success: true,
2665
- message: '${endpointName} data successfully added',
2666
- data: result,
2667
- timestamp: new Date().toISOString()
2668
- });
2669
- } catch (error) {
2670
- console.error('Error saat menambahkan data ${endpointName}:', error);
2671
-
2672
- if (error.errorNum === 1) {
2673
- return res.status(409).json({
2674
- success: false,
2675
- error: 'Duplicate entry',
2676
- message: 'A record with this value already exists',
2677
- timestamp: new Date().toISOString()
2678
- });
2679
- }
2680
-
2681
- if (error.errorNum === 2291) {
2682
- return res.status(400).json({
2683
- success: false,
2684
- error: 'Foreign key constraint',
2685
- message: 'Referenced data not found',
2686
- timestamp: new Date().toISOString()
2687
- });
2688
- }
2689
-
2690
- return res.status(500).json({
2691
- success: false,
2692
- error: 'Internal Server Error',
2693
- message: 'An error occurred while adding ${endpointName} data',
2694
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2695
- timestamp: new Date().toISOString()
2696
- });
2697
- }
2698
- });
2699
-
2700
- `;
2701
- }
2702
-
2703
- // POST /update
2704
- if (payload.action && payload.action.update) {
2705
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/update - Oracle Update
2706
- router.post('/update', async (req, res) => {
2707
- try {
2708
- // Validasi payload
2709
- if (!req.body || Object.keys(req.body).length === 0) {
2710
- return res.status(400).json({
2711
- success: false,
2712
- error: 'Invalid payload',
2713
- message: 'Payload cannot be empty',
2714
- timestamp: new Date().toISOString()
2715
- });
2716
- }
2717
-
2718
- // Validasi primary key
2719
- const primaryKey = '${primaryKey}';
2720
- if (!req.body[primaryKey]) {
2721
- return res.status(400).json({
2722
- success: false,
2723
- error: 'Missing required field',
2724
- message: \`Primary key (\${primaryKey}) is required for update\`,
2725
- timestamp: new Date().toISOString()
2726
- });
2727
- }
2728
- ${requestScopeOwnershipCheckStandard}
2729
- // Validasi data dengan model jika tersedia
2730
- if (typeof ${modelVarName}.validateData === 'function') {
2731
- const validation = await ${modelVarName}.validateData(req.body, 'update');
2732
- if (!validation.isValid) {
2733
- return res.status(400).json({
2734
- success: false,
2735
- error: 'Validation failed',
2736
- message: 'Invalid data',
2737
- errors: validation.errors,
2738
- timestamp: new Date().toISOString()
2739
- });
2740
- }
2741
- req.body = { ...req.body, ...validation.sanitizedData };
2742
- }
2743
-
2744
- let responseData = null;
2745
-
2746
- ${hasComponents ? `
2747
- // Integrated transaction dengan event lifecycle
2748
- try {
2749
- const eventContext = {
2750
- componentEngine: componentEngine,
2751
- ContextBuilder: ContextBuilder,
2752
- tableName: '${payload.tableName}',
2753
- additionalContext: {
2754
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
2755
- options: req.bodyOptions || {}
2756
- }
2757
- };
2758
- responseData = await ${modelVarName}.updateData(req.body, eventContext${policyArg});
2759
- console.log('[INTEGRATED TRANSACTION] UPDATE completed successfully with events');
2760
- } catch (error) {
2761
- console.error('[INTEGRATED TRANSACTION] UPDATE failed:', error.message);
2762
- throw error;
2763
- }
2764
- ` : `
2765
- // Fallback: mode tanpa events
2766
- try {
2767
- responseData = await ${modelVarName}.updateData(req.body, { additionalContext: { requestId: req.id || null } }${policyArg});
2768
- console.log('[FALLBACK] UPDATE completed without events');
2769
- } catch (error) {
2770
- console.error('[FALLBACK] UPDATE failed:', error.message);
2771
- throw error;
2772
- }
2773
- `}
2774
- // Log successful operation
2775
- console.log(\`${endpointName} data updated successfully: ${primaryKey}=\${req.body['${primaryKey}']}\`);
2776
-
2777
- return res.status(200).json({
2778
- success: true,
2779
- message: '${endpointName} data successfully updated',
2780
- data: responseData,
2781
- timestamp: new Date().toISOString()
2782
- });
2783
- } catch (error) {
2784
- console.error('Error saat mengupdate data ${endpointName}:', error);
2785
-
2786
- if (error.message === 'Data tidak ditemukan' || error.message.includes('not found')) {
2787
- return res.status(404).json({
2788
- success: false,
2789
- error: 'Data not found',
2790
- message: '${endpointName} data not found',
2791
- timestamp: new Date().toISOString()
2792
- });
2793
- }
2794
-
2795
- if (error.errorNum === 1) {
2796
- return res.status(409).json({
2797
- success: false,
2798
- error: 'Duplicate entry',
2799
- message: 'A record with this value already exists',
2800
- timestamp: new Date().toISOString()
2801
- });
2802
- }
2803
-
2804
- return res.status(500).json({
2805
- success: false,
2806
- error: 'Internal Server Error',
2807
- message: 'An error occurred while updating ${endpointName} data',
2808
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2809
- timestamp: new Date().toISOString()
2810
- });
2811
- }
2812
- });
2813
-
2814
- `;
2815
- }
2816
-
2817
- // POST /adjust
2818
- if (payload.action && payload.action.adjust) {
2819
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/adjust - Oracle Adjust (atomic increment/decrement)
2820
- router.post('/adjust', async (req, res) => {
2821
- try {
2822
- // Validasi payload
2823
- if (!req.body || Object.keys(req.body).length === 0) {
2824
- return res.status(400).json({
2825
- success: false,
2826
- error: 'Invalid payload',
2827
- message: 'Payload cannot be empty',
2828
- timestamp: new Date().toISOString()
2829
- });
2830
- }
2831
-
2832
- // Validasi primary key
2833
- const primaryKey = '${primaryKey}';
2834
- if (!req.body[primaryKey]) {
2835
- return res.status(400).json({
2836
- success: false,
2837
- error: 'Missing required field',
2838
- message: \`Primary key (\${primaryKey}) is required for adjust\`,
2839
- timestamp: new Date().toISOString()
2840
- });
2841
- }
2842
-
2843
- // Validasi adjustments array
2844
- if (!req.body.adjustments || !Array.isArray(req.body.adjustments) || req.body.adjustments.length === 0) {
2845
- return res.status(400).json({
2846
- success: false,
2847
- error: 'Invalid payload',
2848
- message: 'adjustments array is required and must not be empty',
2849
- timestamp: new Date().toISOString()
2850
- });
2851
- }
2852
- ${requestScopeOwnershipCheckStandard}
2853
- const adjustConfig = componentConfig.adjustConfig || {};
2854
- let responseData = null;
2855
-
2856
- ${hasComponents ? `
2857
- // Integrated transaction dengan event lifecycle
2858
- try {
2859
- const eventContext = {
2860
- componentEngine: componentEngine,
2861
- ContextBuilder: ContextBuilder,
2862
- tableName: '${payload.tableName}',
2863
- additionalContext: {
2864
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
2865
- options: req.bodyOptions || {}
2866
- }
2867
- };
2868
- responseData = await ${modelVarName}.adjustData(req.body, adjustConfig, eventContext${policyArg});
2869
- console.log('[INTEGRATED TRANSACTION] ADJUST completed successfully with events');
2870
- } catch (error) {
2871
- console.error('[INTEGRATED TRANSACTION] ADJUST failed:', error.message);
2872
- throw error;
2873
- }
2874
- ` : `
2875
- // Fallback: mode tanpa events
2876
- try {
2877
- responseData = await ${modelVarName}.adjustData(req.body, adjustConfig, { additionalContext: { requestId: req.id || null } }${policyArg});
2878
- console.log('[FALLBACK] ADJUST completed without events');
2879
- } catch (error) {
2880
- console.error('[FALLBACK] ADJUST failed:', error.message);
2881
- throw error;
2882
- }
2883
- `}
2884
-
2885
- // Log successful operation
2886
- console.log(\`${endpointName} data adjusted successfully: ${primaryKey}=\${req.body['${primaryKey}']}\`);
2887
-
2888
- return res.status(200).json({
2889
- success: true,
2890
- message: '${endpointName} data successfully adjusted',
2891
- data: responseData,
2892
- timestamp: new Date().toISOString()
2893
- });
2894
- } catch (error) {
2895
- console.error('Error saat mengadjust data ${endpointName}:', error);
2896
-
2897
- if (error.statusCode === 403) {
2898
- return res.status(403).json({
2899
- success: false,
2900
- error: 'Pro Feature Required',
2901
- message: error.message,
2902
- upgrade: 'https://restforge.dev/pricing',
2903
- timestamp: new Date().toISOString()
2904
- });
2905
- }
2906
-
2907
- if (error.message.includes('constraint violation') || error.message.includes('below minimum')) {
2908
- return res.status(409).json({
2909
- success: false,
2910
- error: 'Constraint violation',
2911
- message: error.message,
2912
- timestamp: new Date().toISOString()
2913
- });
2914
- }
2915
-
2916
- if (error.message.includes('not configured for adjustment') || error.message.includes('is required for adjust') || error.message.includes('must be a non-zero number') || error.message.includes('not a valid field') || error.message.includes('must not be empty')) {
2917
- return res.status(400).json({
2918
- success: false,
2919
- error: 'Validation error',
2920
- message: error.message,
2921
- timestamp: new Date().toISOString()
2922
- });
2923
- }
2924
-
2925
- if (error.message === 'Data tidak ditemukan' || error.message.includes('not found')) {
2926
- return res.status(404).json({
2927
- success: false,
2928
- error: 'Data not found',
2929
- message: '${endpointName} data not found',
2930
- timestamp: new Date().toISOString()
2931
- });
2932
- }
2933
-
2934
- // Oracle unique constraint violation (ORA-00001). Adjust beroperasi dengan
2935
- // UPDATE atomic sehingga ORA-00001 jarang terpicu, namun handler ini tetap
2936
- // disertakan untuk parity dengan MySQL (ER_DUP_ENTRY) dan PostgreSQL (23505).
2937
- if (error.errorNum === 1) {
2938
- return res.status(409).json({
2939
- success: false,
2940
- error: 'Duplicate entry',
2941
- message: 'A record with this value already exists',
2942
- timestamp: new Date().toISOString()
2943
- });
2944
- }
2945
-
2946
- return res.status(500).json({
2947
- success: false,
2948
- error: 'Internal Server Error',
2949
- message: 'An error occurred while adjusting ${endpointName} data',
2950
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2951
- timestamp: new Date().toISOString()
2952
- });
2953
- }
2954
- });
2955
-
2956
- `;
2957
- }
2958
-
2959
- // POST /change-status (workflow)
2960
- if (payload.action && payload.action.workflow) {
2961
- const workflowConfigStr = payload.workflow ? JSON.stringify(payload.workflow) : '{}';
2962
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/change-status - Oracle Change status ${endpointName}
2963
- router.post('/change-status', async (req, res) => {
2964
- try {
2965
- if (!req.body || Object.keys(req.body).length === 0) {
2966
- return res.status(400).json({
2967
- success: false,
2968
- error: 'Invalid payload',
2969
- message: 'Payload cannot be empty',
2970
- timestamp: new Date().toISOString()
2971
- });
2972
- }
2973
-
2974
- const primaryKey = '${primaryKey}';
2975
- const recordId = req.body[primaryKey] || req.body.id;
2976
- if (!recordId) {
2977
- return res.status(400).json({
2978
- success: false,
2979
- error: 'Missing required field',
2980
- message: \`Primary key (\${primaryKey}) or id is required for change-status\`,
2981
- timestamp: new Date().toISOString()
2982
- });
2983
- }
2984
-
2985
- if (!req.body.status) {
2986
- return res.status(400).json({
2987
- success: false,
2988
- error: 'Missing required field',
2989
- message: 'status is required for change-status',
2990
- timestamp: new Date().toISOString()
2991
- });
2992
- }
2993
- ${requestScopeOwnershipCheckStandard.replace(/req\.body\[primaryKey\]/g, 'recordId')}
2994
- const workflowConfig = ${workflowConfigStr};
2995
- workflowConfig._project = '${moduleName}';
2996
- let result = null;
2997
-
2998
- ${hasComponents ? `
2999
- // Integrated transaction dengan event lifecycle
3000
- try {
3001
- const eventContext = {
3002
- componentEngine: componentEngine,
3003
- ContextBuilder: ContextBuilder,
3004
- tableName: '${payload.tableName}',
3005
- services: {},
3006
- additionalContext: {
3007
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || req.body.updated_by || 'system',
3008
- options: req.bodyOptions || {},
3009
- requestId: req.id || null,
3010
- // JWT forwarding (Layer 1 RLS): Authorization header dari request asli
3011
- // di-forward ke workflow hook call agar endpoint tujuan yang punya
3012
- // requestScope aktif tetap menerima req.user dengan scope yang sama.
3013
- authHeader: req.headers.authorization || null
3014
- }
3015
- };
3016
-
3017
- try {
3018
- const { resolveServices } = require('@restforgejs/platform/src/utils/service-resolver');
3019
- eventContext.services = resolveServices();
3020
- } catch (e) {
3021
- // Service resolver opsional
3022
- }
3023
-
3024
- result = await ${modelVarName}.changeStatusData(req.body, workflowConfig, eventContext);
3025
- console.log('[INTEGRATED TRANSACTION] CHANGE-STATUS completed successfully with events');
3026
- } catch (error) {
3027
- console.error('[INTEGRATED TRANSACTION] CHANGE-STATUS failed:', error.message);
3028
- throw error;
3029
- }
3030
- ` : `
3031
- // Fallback: tanpa component engine events (tetap forward authHeader untuk hook JWT forwarding)
3032
- try {
3033
- const eventContext = {
3034
- additionalContext: {
3035
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || req.body.updated_by || 'system',
3036
- requestId: req.id || null,
3037
- authHeader: req.headers.authorization || null
3038
- }
3039
- };
3040
- result = await ${modelVarName}.changeStatusData(req.body, workflowConfig, eventContext);
3041
- console.log('[FALLBACK] CHANGE-STATUS completed without component events');
3042
- } catch (error) {
3043
- console.error('[FALLBACK] CHANGE-STATUS failed:', error.message);
3044
- throw error;
3045
- }
3046
- `}
3047
-
3048
- console.log(\`${endpointName} status changed: \${result.workflow.previousStatus} → \${result.workflow.newStatus}\`);
3049
-
3050
- return res.status(200).json({
3051
- success: true,
3052
- message: \`Status changed from \${result.workflow.previousStatus} to \${result.workflow.newStatus}\`,
3053
- data: result.data,
3054
- workflow: result.workflow,
3055
- timestamp: new Date().toISOString()
3056
- });
3057
- } catch (error) {
3058
- console.error('Error saat change-status ${endpointName}:', error);
3059
-
3060
- // Status transition not allowed
3061
- if (error.statusCode === 422 || error.message.includes('Cannot change status') || error.message.includes('No transitions defined')) {
3062
- return res.status(422).json({
3063
- success: false,
3064
- error: 'Status transition not allowed',
3065
- message: error.message,
3066
- timestamp: new Date().toISOString()
3067
- });
3068
- }
3069
-
3070
- // Concurrent modification (optimistic concurrency check via status guard clause)
3071
- if (error.code === 'WORKFLOW_CONCURRENT_MODIFICATION' || error.statusCode === 409) {
3072
- return res.status(409).json({
3073
- success: false,
3074
- error: 'Concurrent modification',
3075
- message: error.message,
3076
- timestamp: new Date().toISOString()
3077
- });
3078
- }
3079
-
3080
- // Validation errors
3081
- if (error.message.includes('is required for') || error.message.includes('is not a valid field')) {
3082
- return res.status(400).json({
3083
- success: false,
3084
- error: 'Validation error',
3085
- message: error.message,
3086
- timestamp: new Date().toISOString()
3087
- });
3088
- }
3089
-
3090
- // Hook execution failed
3091
- if (error.message.includes('hook failed') || error.message.includes('onBefore') || error.message.includes('onAfter')) {
3092
- return res.status(502).json({
3093
- success: false,
3094
- error: 'Workflow hook failed',
3095
- message: error.message,
3096
- timestamp: new Date().toISOString()
3097
- });
3098
- }
3099
-
3100
- // Record not found
3101
- if (error.message === 'Record not found' || error.message.includes('not found')) {
3102
- return res.status(404).json({
3103
- success: false,
3104
- error: 'Data not found',
3105
- message: '${endpointName} data not found',
3106
- timestamp: new Date().toISOString()
3107
- });
3108
- }
3109
-
3110
- // Lock acquisition failed
3111
- if (error.message.includes('Failed to acquire lock')) {
3112
- return res.status(409).json({
3113
- success: false,
3114
- error: 'Resource busy',
3115
- message: error.message,
3116
- timestamp: new Date().toISOString()
3117
- });
3118
- }
3119
-
3120
- return res.status(500).json({
3121
- success: false,
3122
- error: 'Internal Server Error',
3123
- message: 'An error occurred while changing status',
3124
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3125
- timestamp: new Date().toISOString()
3126
- });
3127
- }
3128
- });
3129
-
3130
- `;
3131
- }
3132
-
3133
- // POST /aggregate
3134
- if (payload.action && payload.action.aggregate) {
3135
- const aggregateConfigStr = payload.aggregateConfig ? JSON.stringify(payload.aggregateConfig) : '{}';
3136
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/aggregate - Oracle Aggregate (count, sum, avg, min, max)
3137
- router.post('/aggregate', async (req, res) => {
3138
- try {
3139
- const aggregateConfig = ${aggregateConfigStr};
3140
- const result = await ${modelVarName}.aggregateData(req.body || {}, aggregateConfig);
3141
-
3142
- return res.status(200).json({
3143
- success: true,
3144
- data: result,
3145
- timestamp: new Date().toISOString()
3146
- });
3147
- } catch (error) {
3148
- console.error('Error saat mengagregasi data ${endpointName}:', error);
3149
-
3150
- if (error.statusCode === 403) {
3151
- return res.status(403).json({
3152
- success: false,
3153
- error: 'Pro Feature Required',
3154
- message: error.message,
3155
- upgrade: 'https://restforge.dev/pricing',
3156
- timestamp: new Date().toISOString()
3157
- });
3158
- }
3159
-
3160
- if (error.statusCode === 400 ||
3161
- error.message.includes('not a valid field') ||
3162
- error.message.includes('Invalid aggregate function') ||
3163
- error.message.includes('Invalid alias') ||
3164
- error.message.includes('only allowed with COUNT') ||
3165
- error.message.includes('not defined in aggregate config') ||
3166
- error.message.includes('no join definitions found')) {
3167
- return res.status(400).json({
3168
- success: false,
3169
- error: 'Validation error',
3170
- message: error.message,
3171
- timestamp: new Date().toISOString()
3172
- });
3173
- }
3174
-
3175
- return res.status(500).json({
3176
- success: false,
3177
- error: 'Internal Server Error',
3178
- message: 'An error occurred while aggregating ${endpointName} data',
3179
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3180
- timestamp: new Date().toISOString()
3181
- });
3182
- }
3183
- });
3184
-
3185
- `;
3186
- }
3187
-
3188
- // POST /delete
3189
- if (payload.action && payload.action.delete) {
3190
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/delete - Oracle Delete
3191
- router.post('/delete', async (req, res) => {
3192
- try {
3193
- // Validasi request body
3194
- if (!req.body || Object.keys(req.body).length === 0) {
3195
- return res.status(400).json({
3196
- success: false,
3197
- error: 'Invalid payload',
3198
- message: 'Payload cannot be empty',
3199
- timestamp: new Date().toISOString()
3200
- });
3201
- }
3202
-
3203
- if (!req.body.where) {
3204
- return res.status(400).json({
3205
- success: false,
3206
- error: 'Missing required field',
3207
- message: 'Invalid request format: where parameter is required',
3208
- example: {
3209
- "where": [{ "key": "id", "value": "your-id-value" }]
3210
- },
3211
- timestamp: new Date().toISOString()
3212
- });
3213
- }
3214
-
3215
- // Validasi format where
3216
- if (!Array.isArray(req.body.where) && !req.body.where.conditions) {
3217
- return res.status(400).json({
3218
- success: false,
3219
- error: 'Invalid where format',
3220
- message: 'Invalid where format',
3221
- example: {
3222
- "where": [
3223
- { "key": "id", "value": "your-id-value" }
3224
- ]
3225
- },
3226
- timestamp: new Date().toISOString()
3227
- });
3228
- }
3229
-
3230
- let responseData = null;
3231
-
3232
- // Cek apakah data exist sebelum delete dan ambil old data untuk event lifecycle
3233
- // Menggunakan SELECT * dari tabel utama (tanpa explicit select) karena fieldName
3234
- // bisa mengandung kolom dari JOIN (mis. city_name) yang tidak ada di tabel utama
3235
- if (req.body.where && Array.isArray(req.body.where) && req.body.where.length > 0) {
3236
- const firstCondition = req.body.where[0];
3237
- try {
3238
- const existingData = await ${modelVarName}.getData({
3239
- where: [{ key: firstCondition.key, value: firstCondition.value }]
3240
- });
3241
-
3242
- if (!existingData.success || !existingData.data || existingData.data.length === 0) {
3243
- return res.status(404).json({
3244
- success: false,
3245
- error: 'Data not found',
3246
- message: '${endpointName} data not found',
3247
- timestamp: new Date().toISOString()
3248
- });
3249
- }
3250
- } catch (checkError) {
3251
- return res.status(500).json({
3252
- success: false,
3253
- error: 'Verification Failed',
3254
- message: 'Could not verify data existence before delete',
3255
- details: process.env.NODE_ENV === 'development' ? checkError.message : undefined,
3256
- timestamp: new Date().toISOString()
3257
- });
3258
- }
3259
- }
3260
-
3261
- ${hasComponents ? `
3262
- // Integrated transaction dengan event lifecycle
3263
- try {
3264
- const eventContext = {
3265
- componentEngine: componentEngine,
3266
- ContextBuilder: ContextBuilder,
3267
- tableName: '${payload.tableName}',
3268
- additionalContext: {
3269
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
3270
- options: req.bodyOptions || {}
3271
- }
3272
- };
3273
- responseData = await ${modelVarName}.deleteData(req.body, eventContext);
3274
- console.log('[INTEGRATED TRANSACTION] DELETE completed successfully with events');
3275
- } catch (error) {
3276
- console.error('[INTEGRATED TRANSACTION] DELETE failed:', error.message);
3277
- throw error;
3278
- }
3279
- ` : `
3280
- // Fallback: mode tanpa events
3281
- try {
3282
- responseData = await ${modelVarName}.deleteData(req.body, { additionalContext: { requestId: req.id || null } });
3283
- console.log('[FALLBACK] DELETE completed without events');
3284
- } catch (error) {
3285
- console.error('[FALLBACK] DELETE failed:', error.message);
3286
- throw error;
3287
- }
3288
- `}
3289
- // Log successful operation
3290
- console.log(\`${endpointName} data deleted successfully\`);
3291
-
3292
- return res.json({
3293
- ...responseData,
3294
- timestamp: new Date().toISOString()
3295
- });
3296
- } catch (error) {
3297
- console.error('Error saat menghapus data ${endpointName}:', error);
3298
-
3299
- if (error.errorNum === 2292) {
3300
- return res.status(409).json({
3301
- success: false,
3302
- error: 'Foreign key constraint',
3303
- message: 'Cannot delete: record is still referenced by other data',
3304
- timestamp: new Date().toISOString()
3305
- });
3306
- }
3307
-
3308
- return res.status(500).json({
3309
- success: false,
3310
- error: 'Internal Server Error',
3311
- message: 'An error occurred while deleting ${endpointName} data',
3312
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3313
- timestamp: new Date().toISOString()
3314
- });
3315
- }
3316
- });
3317
-
3318
- `;
3319
- }
3320
-
3321
- // POST /first
3322
- if (payload.action && payload.action.first) {
3323
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/first - Mendapatkan data berdasarkan kriteria
3324
- router.post('/first', async (req, res) => {
3325
- try {
3326
- // Normalize: array 1 elemen → object (backward compatible)
3327
- if (Array.isArray(req.body.where) && req.body.where.length === 1) {
3328
- req.body.where = req.body.where[0];
3329
- }
3330
-
3331
- // Validasi where clause — harus object tunggal {key, value}
3332
- if (!req.body.where || typeof req.body.where !== 'object' || Array.isArray(req.body.where)) {
3333
- return res.status(400).json({
3334
- success: false,
3335
- error: 'Missing required field',
3336
- message: 'Property where is required as {key, value} object',
3337
- example: {
3338
- "where": { "key": "${primaryKey}", "value": "your-id-value" },
3339
- "select": ["field1", "field2"]
3340
- },
3341
- timestamp: new Date().toISOString()
3342
- });
3343
- }
3344
-
3345
- // Validasi where.key dan where.value
3346
- if (!req.body.where.key || req.body.where.value === undefined || req.body.where.value === null || req.body.where.value === '') {
3347
- return res.status(400).json({
3348
- success: false,
3349
- error: 'Invalid where format',
3350
- message: 'Where key and value are required',
3351
- example: {
3352
- "where": { "key": "${primaryKey}", "value": "your-id-value" }
3353
- },
3354
- timestamp: new Date().toISOString()
3355
- });
3356
- }
3357
-
3358
- // Tolak format advanced (conditions/logic)
3359
- if (req.body.where.conditions || req.body.where.logic) {
3360
- return res.status(400).json({
3361
- success: false,
3362
- error: 'Invalid where format',
3363
- message: 'Advanced where format is not supported in /first endpoint. Use /read endpoint for complex queries',
3364
- timestamp: new Date().toISOString()
3365
- });
3366
- }
3367
-
3368
- // Validasi where.key ada di validFields
3369
- const validFields = ${JSON.stringify(payload.fieldName)};
3370
- if (!validFields.includes(req.body.where.key)) {
3371
- return res.status(400).json({
3372
- success: false,
3373
- error: 'Invalid where field',
3374
- message: \`Invalid field: \${req.body.where.key}\`,
3375
- validFields: validFields,
3376
- timestamp: new Date().toISOString()
3377
- });
3378
- }
3379
-
3380
- // Validasi select fields jika ada
3381
- if (req.body.select && Array.isArray(req.body.select)) {
3382
- const invalidFields = req.body.select.filter(field => !validFields.includes(field));
3383
-
3384
- if (invalidFields.length > 0) {
3385
- return res.status(400).json({
3386
- success: false,
3387
- error: 'Invalid select fields',
3388
- message: \`Invalid field(s): \${invalidFields.join(', ')}\`,
3389
- validFields: validFields,
3390
- timestamp: new Date().toISOString()
3391
- });
3392
- }
3393
- }
3394
-
3395
- // Convert ke array format untuk kompatibilitas dengan model.getData() → buildComplexWhereClause()
3396
- const getPayload = {
3397
- where: [{ key: req.body.where.key, value: req.body.where.value }],
3398
- select: req.body.select
3399
- };
3400
- const result = await ${modelVarName}.getData(getPayload);
3401
- ${requestScopeOwnershipCheckPostFetch}
3402
- return res.json({
3403
- ...result,
3404
- timestamp: new Date().toISOString()
3405
- });
3406
- } catch (error) {
3407
- console.error('Error saat mendapatkan data ${endpointName}:', error);
3408
- return res.status(500).json({
3409
- success: false,
3410
- error: 'Internal Server Error',
3411
- message: 'An error occurred while fetching ${endpointName} data',
3412
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3413
- timestamp: new Date().toISOString()
3414
- });
3415
- }
3416
- });
3417
-
3418
- `;
3419
- }
3420
-
3421
- // POST /read
3422
- if (payload.action && payload.action.read) {
3423
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/read - Manual pagination endpoint
3424
- router.post('/read', async (req, res) => {
3425
- try {
3426
- // Deteksi mode: paginasi (page dikirim) atau non-paginasi (page tidak dikirim)
3427
- const paginate = req.body.page !== undefined;
3428
- const page = paginate ? parseInt(req.body.page, 10) : null;
3429
- const perPage = paginate ? Math.min(parseInt(req.body.per_page || 10, 10), 100) : null;
3430
- const limit = !paginate ? Math.min(Math.max(parseInt(req.body.limit || 1000, 10), 1), 5000) : null;
3431
- const searchValue = req.body.search_value || '';
3432
- const searchBy = req.body.search_by || 'all';
3433
-
3434
- // Parse sort_columns
3435
- let sort_columns = [];
3436
- if (req.body.sort_columns && Array.isArray(req.body.sort_columns) && req.body.sort_columns.length > 0) {
3437
- sort_columns = req.body.sort_columns.map(item => ({
3438
- column: item.column,
3439
- direction: (item.direction || 'ASC').toUpperCase()
3440
- }));
3441
- }
3442
-
3443
- // Validasi parameter paginasi (hanya jika mode paginasi)
3444
- if (paginate && page < 1) {
3445
- return res.status(400).json({
3446
- success: false,
3447
- error: 'Invalid page',
3448
- message: 'Page must be greater than 0',
3449
- timestamp: new Date().toISOString()
3450
- });
3451
- }
3452
-
3453
- // Proses parameter where dengan format advanced conditions
3454
- let where = null;
3455
- if (req.body.where && typeof req.body.where === 'object') {
3456
- if (Array.isArray(req.body.where) || (req.body.where.conditions && Array.isArray(req.body.where.conditions))) {
3457
- where = req.body.where;
3458
- }
3459
- }
3460
-
3461
- // Proses parameter select untuk kolom selektif
3462
- const validFields = ${JSON.stringify(payload.fieldName || [])};
3463
- let select = null;
3464
- if (req.body.select && Array.isArray(req.body.select)) {
3465
- const invalidFields = req.body.select.filter(field => !validFields.includes(field));
3466
- if (invalidFields.length > 0) {
3467
- return res.status(400).json({
3468
- success: false,
3469
- error: 'Invalid select fields',
3470
- message: 'Invalid field(s): ' + invalidFields.join(', '),
3471
- validFields: validFields,
3472
- timestamp: new Date().toISOString()
3473
- });
3474
- }
3475
- select = req.body.select;
3476
- }
3477
-
3478
- const options = {
3479
- searchValue,
3480
- searchBy,
3481
- sort_columns,
3482
- where: where,
3483
- select: select
3484
- };
3485
-
3486
- if (paginate) {
3487
- options.page = page;
3488
- options.perPage = perPage;
3489
- } else {
3490
- options.limit = limit;
3491
- }
3492
-
3493
- const result = await ${modelVarName}.getList(options);
3494
-
3495
- // Format response berdasarkan mode
3496
- if (paginate) {
3497
- return res.json({
3498
- success: true,
3499
- data: result.data,
3500
- count: result.data ? result.data.length : 0,
3501
- pagination: result.pagination,
3502
- message: 'Data retrieved successfully'
3503
- });
3504
- } else {
3505
- return res.json({
3506
- success: true,
3507
- data: result.data,
3508
- count: result.data ? result.data.length : 0
3509
- });
3510
- }
3511
- } catch (error) {
3512
- console.error('Error in ${endpointName} list:', error);
3513
- const statusCode = error.statusCode || 500;
3514
- return res.status(statusCode).json({
3515
- success: false,
3516
- error: statusCode === 400 ? 'Bad Request' : 'Internal Server Error',
3517
- message: statusCode === 400 ? error.message : 'An error occurred while fetching ${endpointName} list data',
3518
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3519
- timestamp: new Date().toISOString()
3520
- });
3521
- }
3522
- });
3523
-
3524
- `;
3525
- }
3526
-
3527
- // Composite endpoints (master-detail)
3528
- if (payload.masterDetail && payload.masterDetail.enabled) {
3529
- const _detailTableName = payload.masterDetail.detailTable.split('.').pop();
3530
- const _rootKey = endpointName.replace(/-/g, '_');
3531
- const _headerCalc = payload.masterDetail.headerCalculations || null;
3532
- // Fallback: autoCalculateFields.formula → headerCalculations.total_qty.source → empty (skip calculation)
3533
- const _amountFormula = payload.masterDetail?.detailConfig?.autoCalculateFields?.total_amount?.formula || '';
3534
- const _amountFormulaParts = _amountFormula.split('*').map(s => s.trim());
3535
- const _headerCalcQtyField = _headerCalc?.total_qty?.source?.replace('items.', '') || '';
3536
- const _amountQtyField = _amountFormulaParts[0] || _headerCalcQtyField;
3537
- const _amountPriceField = _amountFormulaParts[1] || 'unit_price';
3538
-
3539
- // createComposite endpoint
3540
- if (payload.action && payload.action.createComposite) {
3541
- const _headerCalcCode = _headerCalc ? `
3542
- var headerCalc = ${JSON.stringify(_headerCalc)};
3543
- if (headerCalc.total_items) {
3544
- data.total_items = data.${_detailTableName}.length;
3545
- }
3546
- if (headerCalc.total_qty && headerCalc.total_qty.source) {
3547
- var qtyField = headerCalc.total_qty.source.replace('items.', '');
3548
- data.total_qty = data.${_detailTableName}.reduce(function(sum, item) { return sum + (Number(item[qtyField]) || 0); }, 0);
3549
- }
3550
- ${_amountQtyField ? `if (headerCalc.total_amount) {
3551
- data.total_amount = data.${_detailTableName}.reduce(function(sum, item) {
3552
- var qty = Number(item.${_amountQtyField}) || 0;
3553
- var price = Number(item.${_amountPriceField}) || 0;
3554
- return sum + (qty * price);
3555
- }, 0);
3556
- }` : '// WARNING: headerCalculations.total_amount skipped — no qty field configured'}
3557
- console.log('Calculated totals:', { total_items: data.total_items, total_qty: data.total_qty, total_amount: data.total_amount });
3558
- ` : '';
3559
-
3560
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/create-composite - Oracle Composite create (master-detail)
3561
- router.post('/create-composite', async (req, res) => {
3562
- try {
3563
- console.log('Request body ${endpointName}/create-composite:', JSON.stringify(req.body, null, 2));
3564
-
3565
- if (!req.body || !req.body.${_rootKey}) {
3566
- return res.status(400).json({
3567
- success: false,
3568
- error: 'Invalid payload',
3569
- message: 'Payload must have property "${_rootKey}"',
3570
- timestamp: new Date().toISOString()
3571
- });
3572
- }
3573
-
3574
- var data = req.body.${_rootKey};
3575
-
3576
- // Validasi dan sanitasi header data dengan constraint (termasuk hash)
3577
- if (typeof ${modelVarName}.validateData === 'function') {
3578
- var headerDataForValidation = Object.assign({}, data);
3579
- delete headerDataForValidation.${_detailTableName};
3580
-
3581
- var validation = await ${modelVarName}.validateData(headerDataForValidation, 'insert');
3582
- if (!validation.isValid) {
3583
- return res.status(400).json({
3584
- success: false,
3585
- error: 'Validation failed',
3586
- message: 'Invalid data',
3587
- errors: validation.errors,
3588
- timestamp: new Date().toISOString()
3589
- });
3590
- }
3591
- Object.assign(data, validation.sanitizedData);
3592
- }
3593
-
3594
- if (!data.${_detailTableName} || !Array.isArray(data.${_detailTableName}) || data.${_detailTableName}.length === 0) {
3595
- return res.status(400).json({
3596
- success: false,
3597
- error: 'Invalid payload',
3598
- message: 'Property "${_detailTableName}" must be a non-empty array',
3599
- timestamp: new Date().toISOString()
3600
- });
3601
- }
3602
-
3603
- ${_headerCalcCode}
3604
-
3605
- // Build eventContext untuk composite hooks
3606
- var eventContext = null;
3607
- if (componentEngine && ContextBuilder) {
3608
- eventContext = {
3609
- componentEngine: componentEngine,
3610
- ContextBuilder: ContextBuilder,
3611
- tableName: '${payload.tableName}',
3612
- additionalContext: {
3613
- detailTable: '${payload.masterDetail.detailTable}',
3614
- foreignKey: '${payload.masterDetail.foreignKey}',
3615
- options: req.bodyOptions || {},
3616
- requestId: req.id || null
3617
- }
3618
- };
3619
- }
3620
-
3621
- var result = await ${modelVarName}.createComposite(data, eventContext);
3622
-
3623
- console.log('${endpointName} composite create successful');
3624
-
3625
- return res.status(201).json({
3626
- success: true,
3627
- message: '${endpointName} data successfully created (with detail items)',
3628
- data: result,
3629
- timestamp: new Date().toISOString()
3630
- });
3631
- } catch (error) {
3632
- console.error('Error saat composite create ${endpointName}:', error);
3633
-
3634
- if (error.errorNum === 1) {
3635
- return res.status(409).json({
3636
- success: false,
3637
- error: 'Duplicate entry',
3638
- message: 'A record with this value already exists',
3639
- timestamp: new Date().toISOString()
3640
- });
3641
- }
3642
- if (error.errorNum === 2291) {
3643
- return res.status(400).json({
3644
- success: false,
3645
- error: 'Foreign key constraint',
3646
- message: 'Referenced data not found',
3647
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3648
- timestamp: new Date().toISOString()
3649
- });
3650
- }
3651
-
3652
- return res.status(500).json({
3653
- success: false,
3654
- error: 'Internal Server Error',
3655
- message: 'An error occurred while creating ${endpointName} data',
3656
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3657
- timestamp: new Date().toISOString()
3658
- });
3659
- }
3660
- });
3661
-
3662
- `;
3663
- }
3664
-
3665
- // updateComposite endpoint
3666
- if (payload.action && payload.action.updateComposite) {
3667
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/update-composite - Oracle Composite update (master-detail)
3668
- router.post('/update-composite', async (req, res) => {
3669
- try {
3670
- console.log('Request body ${endpointName}/update-composite:', JSON.stringify(req.body, null, 2));
3671
-
3672
- if (!req.body || !req.body.${_rootKey}) {
3673
- return res.status(400).json({
3674
- success: false,
3675
- error: 'Invalid payload',
3676
- message: 'Payload must have property "${_rootKey}"',
3677
- timestamp: new Date().toISOString()
3678
- });
3679
- }
3680
-
3681
- var data = req.body.${_rootKey};
3682
-
3683
- if (!data.${primaryKey}) {
3684
- return res.status(400).json({
3685
- success: false,
3686
- error: 'Missing required field',
3687
- message: 'Primary key (${primaryKey}) is required for update',
3688
- timestamp: new Date().toISOString()
3689
- });
3690
- }
3691
- ${requestScopeOwnershipCheckMaster}
3692
- // Validasi dan sanitasi header data dengan constraint (termasuk hash)
3693
- if (typeof ${modelVarName}.validateData === 'function') {
3694
- var headerDataForValidation = Object.assign({}, data);
3695
- delete headerDataForValidation.${_detailTableName};
3696
-
3697
- var validation = await ${modelVarName}.validateData(headerDataForValidation, 'update');
3698
- if (!validation.isValid) {
3699
- return res.status(400).json({
3700
- success: false,
3701
- error: 'Validation failed',
3702
- message: 'Invalid data',
3703
- errors: validation.errors,
3704
- timestamp: new Date().toISOString()
3705
- });
3706
- }
3707
- Object.assign(data, validation.sanitizedData);
3708
- }
3709
-
3710
- if (data.${_detailTableName}) {
3711
- if (typeof data.${_detailTableName} !== 'object' || Array.isArray(data.${_detailTableName})) {
3712
- return res.status(400).json({
3713
- success: false,
3714
- error: 'Invalid payload',
3715
- message: 'Property "${_detailTableName}" must be an object with structure {insert: [], update: [], delete: []}',
3716
- timestamp: new Date().toISOString()
3717
- });
3718
- }
3719
- }
3720
-
3721
- // Build eventContext untuk composite hooks
3722
- var eventContext = null;
3723
- if (componentEngine && ContextBuilder) {
3724
- eventContext = {
3725
- componentEngine: componentEngine,
3726
- ContextBuilder: ContextBuilder,
3727
- tableName: '${payload.tableName}',
3728
- additionalContext: {
3729
- detailTable: '${payload.masterDetail.detailTable}',
3730
- foreignKey: '${payload.masterDetail.foreignKey}',
3731
- options: req.bodyOptions || {},
3732
- requestId: req.id || null
3733
- }
3734
- };
3735
- }
3736
-
3737
- var result = await ${modelVarName}.updateComposite(data, eventContext);
3738
-
3739
- console.log('${endpointName} composite update successful: ${primaryKey}=' + data.${primaryKey});
3740
-
3741
- return res.status(200).json({
3742
- success: true,
3743
- message: '${endpointName} data successfully updated (with detail items)',
3744
- data: result,
3745
- timestamp: new Date().toISOString()
3746
- });
3747
- } catch (error) {
3748
- console.error('Error saat composite update ${endpointName}:', error);
3749
-
3750
- if (error.message === 'Record not found' || error.message.includes('not found')) {
3751
- return res.status(404).json({
3752
- success: false,
3753
- error: 'Data not found',
3754
- message: '${endpointName} data not found',
3755
- timestamp: new Date().toISOString()
3756
- });
3757
- }
3758
- if (error.errorNum === 1) {
3759
- return res.status(409).json({ success: false, error: 'Duplicate entry', message: 'A record with this value already exists', timestamp: new Date().toISOString() });
3760
- }
3761
- if (error.errorNum === 2291) {
3762
- return res.status(400).json({ success: false, error: 'Foreign key constraint', message: 'Referenced data not found', details: process.env.NODE_ENV === 'development' ? error.message : undefined, timestamp: new Date().toISOString() });
3763
- }
3764
-
3765
- return res.status(500).json({
3766
- success: false,
3767
- error: 'Internal Server Error',
3768
- message: 'An error occurred while updating ${endpointName} data',
3769
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3770
- timestamp: new Date().toISOString()
3771
- });
3772
- }
3773
- });
3774
-
3775
- `;
3776
- }
3777
-
3778
- // readComposite endpoint
3779
- if (payload.action && payload.action.readComposite) {
3780
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/read-composite - Oracle Read header with detail items
3781
- router.post('/read-composite', async (req, res) => {
3782
- try {
3783
- console.log('Request body ${endpointName}/read-composite:', JSON.stringify(req.body, null, 2));
3784
-
3785
- if (!req.body || Object.keys(req.body).length === 0) {
3786
- return res.status(400).json({ success: false, error: 'Invalid payload', message: 'Payload cannot be empty', timestamp: new Date().toISOString() });
3787
- }
3788
-
3789
- if (!req.body.where) {
3790
- return res.status(400).json({
3791
- success: false,
3792
- error: 'Missing required field',
3793
- message: 'Property where is required',
3794
- example: { "where": [{ "key": "field_name", "value": "field_value" }] },
3795
- timestamp: new Date().toISOString()
3796
- });
3797
- }
3798
-
3799
- if (!Array.isArray(req.body.where) && !req.body.where.conditions) {
3800
- return res.status(400).json({
3801
- success: false,
3802
- error: 'Invalid where format',
3803
- message: 'Invalid where format',
3804
- example: { "where": [{ "key": "${primaryKey}", "value": "your-id-value" }] },
3805
- timestamp: new Date().toISOString()
3806
- });
3807
- }
3808
-
3809
- var result = await ${modelVarName}.readComposite(req.body);
3810
-
3811
- console.log('${endpointName} composite read successful: ' + (result.count || 0) + ' record(s)');
3812
-
3813
- return res.json({
3814
- ...result,
3815
- timestamp: new Date().toISOString()
3816
- });
3817
- } catch (error) {
3818
- console.error('Error saat composite read ${endpointName}:', error);
3819
- return res.status(500).json({
3820
- success: false,
3821
- error: 'Internal Server Error',
3822
- message: 'An error occurred while reading ${endpointName} composite data',
3823
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3824
- timestamp: new Date().toISOString()
3825
- });
3826
- }
3827
- });
3828
-
3829
- `;
3830
- }
3831
- }
3832
-
3833
- // GET /info — self-documenting API
3834
- if (payload.action?.info !== false) {
3835
- const infoActions = {
3836
- datatables: !!(payload.action?.datatables),
3837
- read: !!(payload.action?.read),
3838
- first: !!(payload.action?.first),
3839
- create: !!(payload.action?.create),
3840
- update: !!(payload.action?.update),
3841
- delete: !!(payload.action?.delete),
3842
- lookup: !!(payload.action?.lookup),
3843
- export: !!(payload.action?.export),
3844
- import: !!(payload.action?.import),
3845
- info: payload.action?.info !== false
3846
- };
3847
-
3848
- subModuleContent += `// Oracle endpoint information — self-documenting API
3849
- router.get('/info', async (req, res) => {
3850
- try {
3851
- const actions = ${JSON.stringify(infoActions)};
3852
- const modelInfo = await ${modelVarName}.getModelInfo(actions);
3853
-
3854
- res.json({
3855
- success: true,
3856
- endpoint: '${endpointName}',
3857
- module: '${moduleName}',
3858
- table: modelInfo.table,
3859
- fields: modelInfo.fields,
3860
- querySources: modelInfo.querySources,
3861
- actions: actions,
3862
- databaseType: 'oracle',
3863
- generated: '${timestamp}',
3864
- timestamp: new Date().toISOString()
3865
- });
3866
- } catch (error) {
3867
- console.error('Oracle info error:', error);
3868
- res.status(500).json({
3869
- success: false,
3870
- error: 'Info Error',
3871
- message: 'An error occurred while fetching endpoint info',
3872
- timestamp: new Date().toISOString()
3873
- });
3874
- }
3875
- });`;
3876
- } else {
3877
- subModuleContent += `// Endpoint /info dinonaktifkan via action.info = false\n`;
3878
- }
3879
-
3880
- subModuleContent += `
3881
- // Oracle health check
3882
- router.get('/health', async (req, res) => {
3883
- try {
3884
- const connectionInfo = await ${modelVarName}.getConnectionInfo();
3885
-
3886
- res.json({
3887
- status: connectionInfo ? 'healthy' : 'unknown',
3888
- endpoint: '${endpointName}',
3889
- database: 'oracle',
3890
- connection: connectionInfo ? 'active' : 'unknown',
3891
- timestamp: new Date().toISOString()
3892
- });
3893
- } catch (error) {
3894
- res.status(503).json({
3895
- status: 'unhealthy',
3896
- endpoint: '${endpointName}',
3897
- database: 'oracle',
3898
- error: error.message,
3899
- timestamp: new Date().toISOString()
3900
- });
3901
- }
3902
- });
3903
-
3904
- `;
3905
-
3906
- subModuleContent += `module.exports = router;`;
3907
-
3908
- return subModuleContent;
3909
- }
3910
-
3911
- module.exports = {
3912
- createOracleMainModuleTemplate,
3913
- createOracleModelTemplate,
3914
- createOracleSubmoduleTemplate
3915
- };
1
+ const a0_0x364e7c=a0_0x1e54;(function(_0x41db2b,_0x1ef5a1){const _0x275853=a0_0x1e54,_0x5bb013=_0x41db2b();while(!![]){try{const _0x1dd0fa=parseInt(_0x275853(0x197))/0x1+parseInt(_0x275853(0x18d))/0x2*(-parseInt(_0x275853(0x1ea))/0x3)+parseInt(_0x275853(0x172))/0x4+-parseInt(_0x275853(0x1a6))/0x5*(parseInt(_0x275853(0x15f))/0x6)+-parseInt(_0x275853(0x1fb))/0x7+parseInt(_0x275853(0x1eb))/0x8+-parseInt(_0x275853(0x1a0))/0x9;if(_0x1dd0fa===_0x1ef5a1)break;else _0x5bb013['push'](_0x5bb013['shift']());}catch(_0x31f77d){_0x5bb013['push'](_0x5bb013['shift']());}}}(a0_0x2998,0x3c638));function toCamelCase(_0x316fca){const _0x2d95f6=a0_0x1e54,_0x2cd6b9={'DTfkL':function(_0x299d11,_0x1f6df9){return _0x299d11===_0x1f6df9;},'CHjCA':function(_0x2e5d0b,_0x43a95e){return _0x2e5d0b!==_0x43a95e;}};if(!_0x316fca||_0x2cd6b9[_0x2d95f6(0x203)](typeof _0x316fca,'string'))return'';return _0x316fca['replace'](/(?:^\w|[A-Z]|\b\w)/g,(_0x43f8b5,_0xb8447f)=>{const _0xfe5118=_0x2d95f6;return _0x2cd6b9['DTfkL'](_0xb8447f,0x0)?_0x43f8b5[_0xfe5118(0x225)]():_0x43f8b5[_0xfe5118(0x224)]();})['replace'](/\s+/g,'')[_0x2d95f6(0x138)](/[-_]/g,'');}function a0_0x1e54(_0x237bb5,_0x4195f4){_0x237bb5=_0x237bb5-0x127;const _0x2998de=a0_0x2998();let _0x1e548a=_0x2998de[_0x237bb5];if(a0_0x1e54['HNiaNa']===undefined){var _0x4a9203=function(_0x54ff12){const _0x47670e='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x2dd503='',_0x95e416='';for(let _0x161528=0x0,_0x4b9e08,_0x4cb04a,_0x21cd96=0x0;_0x4cb04a=_0x54ff12['charAt'](_0x21cd96++);~_0x4cb04a&&(_0x4b9e08=_0x161528%0x4?_0x4b9e08*0x40+_0x4cb04a:_0x4cb04a,_0x161528++%0x4)?_0x2dd503+=String['fromCharCode'](0xff&_0x4b9e08>>(-0x2*_0x161528&0x6)):0x0){_0x4cb04a=_0x47670e['indexOf'](_0x4cb04a);}for(let _0x5f25f0=0x0,_0x5c0bde=_0x2dd503['length'];_0x5f25f0<_0x5c0bde;_0x5f25f0++){_0x95e416+='%'+('00'+_0x2dd503['charCodeAt'](_0x5f25f0)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x95e416);};a0_0x1e54['pbvFNN']=_0x4a9203,a0_0x1e54['FENdIv']={},a0_0x1e54['HNiaNa']=!![];}const _0x19d518=_0x2998de[0x0],_0x25d82e=_0x237bb5+_0x19d518,_0x1c496e=a0_0x1e54['FENdIv'][_0x25d82e];return!_0x1c496e?(_0x1e548a=a0_0x1e54['pbvFNN'](_0x1e548a),a0_0x1e54['FENdIv'][_0x25d82e]=_0x1e548a):_0x1e548a=_0x1c496e,_0x1e548a;}function toPascalCase(_0x1aea5d){const _0xdbacd=a0_0x1e54,_0x27e7a8={'tUCgR':'string','ozOXC':function(_0xbc3cdd,_0x5a1a4c){return _0xbc3cdd(_0x5a1a4c);},'QuqFK':function(_0x5cdf3c,_0x1d76dc){return _0x5cdf3c+_0x1d76dc;}};if(!_0x1aea5d||typeof _0x1aea5d!==_0x27e7a8['tUCgR'])return'';const _0x1afd6c=_0x27e7a8[_0xdbacd(0x1fd)](toCamelCase,_0x1aea5d);return _0x27e7a8['QuqFK'](_0x1afd6c['charAt'](0x0)[_0xdbacd(0x224)](),_0x1afd6c[_0xdbacd(0x154)](0x1));}function detectTextColumn(_0x4f8ae4){const _0x5225f6=a0_0x1e54,_0x1bc81f={'IMqMu':'name','LzFXX':_0x5225f6(0x1e2),'OcmlB':'deskripsi','ZGhJz':'code','SuiVI':_0x5225f6(0x12a)},_0x56376b=[_0x1bc81f[_0x5225f6(0x165)],_0x1bc81f[_0x5225f6(0x146)],_0x5225f6(0x249),_0x5225f6(0x1c7),_0x5225f6(0x158),_0x1bc81f[_0x5225f6(0x18f)],_0x1bc81f['ZGhJz'],_0x1bc81f['SuiVI']];for(const _0x14b74a of _0x56376b){const _0x3c2157=_0x4f8ae4['find'](_0x14dbf8=>_0x14dbf8['toLowerCase']()[_0x5225f6(0x1c3)](_0x14b74a));if(_0x3c2157)return _0x3c2157;}return _0x4f8ae4['find'](_0x3de230=>_0x3de230!=='id')||_0x4f8ae4[0x0]||'nama';}function buildAuditColumnsSection(_0x531190,_0x55df47){const _0x5f1339=a0_0x1e54,_0x1893f5={'PQCMi':_0x5f1339(0x171),'ECGpr':_0x5f1339(0x1c6),'KdbmB':'createdAt','shkYN':'updatedBy'};if(!(_0x1893f5[_0x5f1339(0x233)]in _0x531190))return'';const _0x4bed7d=_0x531190[_0x5f1339(0x171)];if(_0x4bed7d===![]||_0x4bed7d===null)return'\x0a'+_0x55df47+'//\x20Tabel\x20tanpa\x20audit\x20columns:\x20disable\x20injection\x20helper\x0a'+_0x55df47+_0x5f1339(0x23c);if(typeof _0x4bed7d===_0x1893f5['ECGpr']&&!Array[_0x5f1339(0x1c4)](_0x4bed7d)){const _0x3153cb=[_0x1893f5['KdbmB'],_0x5f1339(0x256),_0x5f1339(0x141),_0x1893f5['shkYN']],_0x5192d2={};_0x3153cb['forEach'](_0x2f745c=>{if(_0x4bed7d[_0x2f745c]!==undefined)_0x5192d2[_0x2f745c]=_0x4bed7d[_0x2f745c];});const _0x3e63bf=JSON[_0x5f1339(0x1f8)](_0x5192d2,null,0x2)[_0x5f1339(0x1ac)]('\x0a')[_0x5f1339(0x25d)]((_0x33664f,_0xb96cf0)=>_0xb96cf0===0x0?_0x33664f:_0x55df47+_0x33664f)['join']('\x0a');return'\x0a'+_0x55df47+_0x5f1339(0x152)+_0x55df47+_0x5f1339(0x236)+_0x3e63bf+';\x0a';}throw new Error(_0x5f1339(0x1e8)+_0x531190['tableName']+':\x20must\x20be\x20false,\x20null,\x20or\x20object');}function createOracleMainModuleTemplate(_0x559b27){const _0x382d0c=a0_0x1e54,_0x5c6d5d=toPascalCase(_0x559b27);return'const\x20express\x20=\x20require(\x27express\x27);\x0aconst\x20bodyParser\x20=\x20require(\x27body-parser\x27);\x0aconst\x20path\x20=\x20require(\x27path\x27);\x0aconst\x20fs\x20=\x20require(\x27fs\x27);\x0aconst\x20{\x20v4:\x20uuidv4\x20}\x20=\x20require(\x27uuid\x27);\x0aconst\x20{\x20logger,\x20logServerReady,\x20logEndpointRegistered,\x20createRequestLogger,\x20logRequest\x20}\x20=\x20require(\x27@restforgejs/platform/src/utils/logger\x27);\x0aconst\x20ExportHandler\x20=\x20require(\x27@restforgejs/platform/src/components/handlers/export_handler\x27);\x0aconst\x20ImportHandler\x20=\x20require(\x27@restforgejs/platform/src/components/handlers/import_handler\x27);\x0aconst\x20UploadHandler\x20=\x20require(\x27@restforgejs/platform/src/components/handlers/upload_handler\x27);\x0aconst\x20{\x20extractExportConfigFromEndpoint,\x20extractImportConfigFromEndpoint,\x20extractUploadConfigFromEndpoint\x20}\x20=\x20require(\x27@restforgejs/platform/src/utils/config-extractor\x27);\x0aconst\x20rateLimiter\x20=\x20require(\x27@restforgejs/platform/src/middleware/rate-limiter\x27);\x0aconst\x20idempotencyMiddleware\x20=\x20require(\x27@restforgejs/platform/src/middleware/idempotency\x27);\x0aconst\x20bodyOptionsMiddleware\x20=\x20require(\x27@restforgejs/platform/src/middleware/body-options\x27);\x0aconst\x20corsMiddleware\x20=\x20require(\x27@restforgejs/platform/src/middleware/cors\x27);\x0aconst\x20securityHeaders\x20=\x20require(\x27@restforgejs/platform/src/middleware/security-headers\x27);\x0a\x0a/**\x0a\x20*\x20Fungsi\x20untuk\x20mengeksekusi\x20modul\x20'+_0x559b27+'\x20(Oracle\x20Database)\x0a\x20*\x20@param\x20{Object}\x20config\x20-\x20Konfigurasi\x20untuk\x20menjalankan\x20modul\x0a\x20*\x20@param\x20{number}\x20config.port\x20-\x20Port\x20untuk\x20server\x0a\x20*\x20@param\x20{string}\x20config.key\x20-\x20API\x20Key\x20(opsional)\x0a\x20*\x20@returns\x20{Promise<void>}\x20Promise\x20yang\x20tidak\x20pernah\x20resolve\x20agar\x20server\x20tetap\x20berjalan\x0a\x20*/\x0aasync\x20function\x20execute(config)\x20{\x0a\x20\x20return\x20new\x20Promise((resolve)\x20=>\x20{\x0a\x20\x20\x20\x20const\x20app\x20=\x20express();\x0a\x20\x20\x20\x20const\x20port\x20=\x20config.port\x20||\x203000;\x0a\x20\x20\x20\x20const\x20serverAddress\x20=\x20config.serverAddress\x20||\x20\x270.0.0.0\x27;\x0a\x20\x20\x20\x20const\x20moduleNameCapitalized\x20=\x20\x27'+_0x5c6d5d+'\x27;\x0a\x0a\x20\x20\x20\x20//\x20Configuration\x20options\x0a\x20\x20\x20\x20const\x20loggingEnabled\x20=\x20config.logging\x20!==\x20false;\x0a\x20\x20\x20\x20const\x20apiKeyRequired\x20=\x20!!config.key;\x0a\x0a\x20\x20\x20\x20logger.info({\x0a\x20\x20\x20\x20\x20\x20event:\x20\x27module_starting\x27,\x0a\x20\x20\x20\x20\x20\x20module:\x20moduleNameCapitalized,\x0a\x20\x20\x20\x20\x20\x20port,\x0a\x20\x20\x20\x20\x20\x20cors:\x20process.env.CORS_ENABLED\x20!==\x20\x27false\x27,\x0a\x20\x20\x20\x20\x20\x20helmet:\x20process.env.HELMET_ENABLED\x20===\x20\x27true\x27,\x0a\x20\x20\x20\x20\x20\x20logging:\x20loggingEnabled,\x0a\x20\x20\x20\x20\x20\x20apiKey:\x20apiKeyRequired\x0a\x20\x20\x20\x20},\x20`Starting\x20${moduleNameCapitalized}\x20module`);\x0a\x0a\x20\x20\x20\x20//\x20CORS\x20middleware\x20(konfigurasi\x20via\x20CORS_ENABLED\x20dan\x20CORS_ORIGINS\x20di\x20.env)\x0a\x20\x20\x20\x20app.use(corsMiddleware.middleware());\x0a\x0a\x20\x20\x20\x20//\x20Security\x20headers\x20middleware\x20(konfigurasi\x20via\x20HELMET_ENABLED\x20di\x20.env)\x0a\x20\x20\x20\x20app.use(securityHeaders.middleware());\x0a\x0a\x20\x20\x20\x20//\x20Middleware\x20untuk\x20parsing\x20JSON\x20dengan\x20penanganan\x20error\x0a\x20\x20\x20\x20app.use(bodyParser.json({\x0a\x20\x20\x20\x20\x20\x20verify:\x20(req,\x20res,\x20buf,\x20encoding)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(buf.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20JSON.parse(buf);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20JSON\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27The\x20payload\x20sent\x20is\x20not\x20a\x20valid\x20JSON\x20format\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20details:\x20error.message\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27Invalid\x20JSON\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}));\x0a\x0a\x20\x20\x20\x20app.use(bodyParser.urlencoded({\x20extended:\x20true\x20}));\x0a\x0a\x20\x20\x20\x20//\x20Request\x20logging\x20middleware\x20(Pino-based)\x0a\x20\x20\x20\x20app.use((req,\x20res,\x20next)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20req.id\x20=\x20req.headers[\x27x-request-id\x27]\x20||\x20uuidv4();\x0a\x20\x20\x20\x20\x20\x20res.set(\x27X-Request-ID\x27,\x20req.id);\x0a\x0a\x20\x20\x20\x20\x20\x20req.log\x20=\x20createRequestLogger({\x0a\x20\x20\x20\x20\x20\x20\x20\x20requestId:\x20req.id,\x0a\x20\x20\x20\x20\x20\x20\x20\x20method:\x20req.method,\x0a\x20\x20\x20\x20\x20\x20\x20\x20path:\x20req.path,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ip:\x20req.ip\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20startTime\x20=\x20process.hrtime();\x0a\x0a\x20\x20\x20\x20\x20\x20res.on(\x27finish\x27,\x20()\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20[seconds,\x20nanoseconds]\x20=\x20process.hrtime(startTime);\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20durationMs\x20=\x20parseFloat((seconds\x20*\x201000\x20+\x20nanoseconds\x20/\x201e6).toFixed(2));\x0a\x20\x20\x20\x20\x20\x20\x20\x20logRequest(req,\x20res,\x20durationMs);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20next();\x0a\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20//\x20Middleware\x20untuk\x20validasi\x20API\x20key\x20jika\x20diperlukan\x20(constant-time\x20comparison)\x0a\x20\x20\x20\x20if\x20(config.key)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20crypto\x20=\x20require(\x27crypto\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20expectedKey\x20=\x20Buffer.from(config.key);\x0a\x0a\x20\x20\x20\x20\x20\x20app.use((req,\x20res,\x20next)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20apiKey\x20=\x20req.headers[\x27x-api-key\x27];\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!apiKey)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(401).json({\x20error:\x20\x27Unauthorized:\x20Invalid\x20API\x20Key\x27\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20providedKey\x20=\x20Buffer.from(apiKey);\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(expectedKey.length\x20!==\x20providedKey.length\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20!crypto.timingSafeEqual(expectedKey,\x20providedKey))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(401).json({\x20error:\x20\x27Unauthorized:\x20Invalid\x20API\x20Key\x27\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20next();\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Rate\x20limiting\x20middleware\x20(store:\x20memory\x20untuk\x20single\x20mode,\x20Redis\x20untuk\x20cluster\x20mode)\x0a\x20\x20\x20\x20rateLimiter.setStore(config.cluster\x20?\x20\x27redis\x27\x20:\x20\x27memory\x27);\x0a\x20\x20\x20\x20app.use(\x27/api\x27,\x20rateLimiter.middleware());\x0a\x0a\x20\x20\x20\x20//\x20Idempotency\x20middleware\x20(protects\x20mutation\x20endpoints\x20from\x20duplicate\x20execution)\x0a\x20\x20\x20\x20if\x20(process.env.IDEMPOTENCY_ENABLED\x20===\x20\x27true\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20app.use(\x27/api\x27,\x20idempotencyMiddleware.middleware());\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Body\x20options\x20middleware\x20(extract\x20{data,\x20options}\x20format\x20dari\x20request\x20body)\x0a\x20\x20\x20\x20app.use(\x27/api\x27,\x20bodyOptionsMiddleware.middleware());\x0a\x0a\x20\x20\x20\x20//\x20Auto-load\x20plugin\x20(jika\x20ada)\x0a\x20\x20\x20\x20const\x20moduleName\x20=\x20\x27'+_0x559b27+'\x27;\x0a\x20\x20\x20\x20const\x20pluginPath\x20=\x20path.join(__dirname,\x20\x27..\x27,\x20\x27plugins\x27,\x20`${moduleName}-plugin.js`);\x0a\x20\x20\x20\x20let\x20plugin\x20=\x20null;\x0a\x20\x20\x20\x20if\x20(fs.existsSync(pluginPath))\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20plugin\x20=\x20require(pluginPath);\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(plugin.onBeforeEndpointsLoad)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20plugin.onBeforeEndpointsLoad(app,\x20config);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20logger.info({\x20event:\x20\x27plugin_loaded\x27,\x20plugin:\x20`${moduleName}-plugin`\x20},\x20`Plugin\x20loaded:\x20${moduleName}-plugin.js`);\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(pluginError)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20logger.error({\x20event:\x20\x27plugin_load_error\x27,\x20error:\x20pluginError.message\x20},\x20`Failed\x20to\x20load\x20plugin:\x20${moduleName}-plugin.js`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Health\x20check\x20endpoint\x0a\x20\x20\x20\x20app.get(\x27/api/'+_0x559b27+_0x382d0c(0x251)+_0x559b27+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20uptime:\x20process.uptime(),\x0a\x20\x20\x20\x20\x20\x20\x20\x20memory:\x20process.memoryUsage(),\x0a\x20\x20\x20\x20\x20\x20\x20\x20system:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20platform:\x20process.platform,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20nodeVersion:\x20process.version,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20pid:\x20process.pid\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20\x20\x20res.json(healthInfo);\x0a\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20//\x20Muat\x20semua\x20rute\x20dari\x20folder\x20'+_0x559b27+'\x0a\x20\x20\x20\x20const\x20modulesDir\x20=\x20path.join(__dirname,\x20\x27'+_0x559b27+_0x382d0c(0x15b)+_0x559b27+'/${endpointName}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20app.use(endpointPrefix,\x20moduleRoutes);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20logEndpointRegistered(endpointName,\x20endpointPrefix);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Register\x20export\x20routes\x20via\x20centralized\x20handler\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20exportConfig\x20=\x20extractExportConfigFromEndpoint(endpointPath);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(exportConfig)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ExportHandler.registerRoutes(app,\x20\x27'+_0x559b27+'\x27,\x20endpointName,\x20exportConfig);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x20catch\x20(exportError)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20logger.error({\x20event:\x20\x27export_registration_error\x27,\x20endpoint:\x20endpointName,\x20error:\x20exportError.message\x20},\x20`Export\x20registration\x20failed\x20for\x20${endpointName}:\x20${exportError.message}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Register\x20import\x20routes\x20via\x20centralized\x20handler\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20importConfig\x20=\x20extractImportConfigFromEndpoint(endpointPath);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(importConfig)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ImportHandler.registerRoutes(app,\x20\x27'+_0x559b27+_0x382d0c(0x168)+_0x559b27+'\x27,\x20endpointName,\x20uploadConfig);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20logger.info({\x20event:\x20\x27upload_routes_registered\x27,\x20endpoint:\x20endpointName,\x20fields:\x20Object.keys(uploadConfig.fields\x20||\x20{})\x20},\x20`Upload\x20routes\x20registered\x20for\x20${endpointName}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x20catch\x20(uploadError)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20logger.error({\x20event:\x20\x27upload_registration_error\x27,\x20endpoint:\x20endpointName,\x20error:\x20uploadError.message\x20},\x20`Upload\x20registration\x20failed\x20for\x20${endpointName}:\x20${uploadError.message}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20console.error(`Error\x20loading\x20module\x20${file}\x20from\x20'+_0x559b27+_0x382d0c(0x136)+_0x559b27+'\x20API\x20(Oracle\x20Database)\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20status:\x20\x27running\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20database:\x20\x27Oracle\x27\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Error\x20handling\x20middleware\x0a\x20\x20\x20\x20\x20\x20app.use((err,\x20req,\x20res,\x20next)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(\x27Error:\x27,\x20err);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(err\x20instanceof\x20SyntaxError\x20&&\x20err.status\x20===\x20400\x20&&\x20\x27body\x27\x20in\x20err\x20&&\x20!res.headersSent)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20JSON\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27The\x20payload\x20sent\x20is\x20not\x20a\x20valid\x20JSON\x20format\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20details:\x20err.message\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!res.headersSent)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20on\x20the\x20server\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20details:\x20err.message\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20next(err);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Hook\x20untuk\x20mount\x20middleware\x20tambahan\x20sebelum\x20catch-all\x20403\x0a\x20\x20\x20\x20\x20\x20//\x20Digunakan\x20oleh\x20runtime\x20untuk\x20register\x20admin\x20endpoint\x20dan\x20middleware\x20eksternal\x20lainnya\x0a\x20\x20\x20\x20\x20\x20if\x20(config.onAppReady\x20&&\x20typeof\x20config.onAppReady\x20===\x20\x27function\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20config.onAppReady(app);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20app.use((req,\x20res)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20res.status(403).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Forbidden\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Access\x20to\x20the\x20requested\x20resource\x20is\x20forbidden\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20server\x20=\x20app.listen(port,\x20serverAddress,\x20(err)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(err)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20console.error(`Failed\x20to\x20start\x20${moduleNameCapitalized}\x20server:`,\x20err);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Determine\x20display\x20URL\x20based\x20on\x20serverAddress\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20displayHost\x20=\x20(serverAddress\x20===\x20\x270.0.0.0\x27\x20||\x20!serverAddress)\x20?\x20\x27localhost\x27\x20:\x20serverAddress;\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20logServerReady({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20port,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20module:\x20\x27'+_0x559b27+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20healthCheck:\x20`http://${displayHost}:${port}/api/'+_0x559b27+'/health`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20serviceInfo:\x20`http://${displayHost}:${port}/api/'+_0x559b27+_0x382d0c(0x212)+_0x559b27+_0x382d0c(0x239);}function buildDefaultScopeSQL(_0x516cc2,_0x9c495f=''){const _0x230e9e=a0_0x1e54,_0x58c230={'apJjq':function(_0x377f2e,_0x527edb){return _0x377f2e!==_0x527edb;},'MeLBq':_0x230e9e(0x1c6),'HNIDA':'boolean','wsjiK':function(_0x270db4,_0x133eb3){return _0x270db4===_0x133eb3;},'NHowU':function(_0x153ab4,_0x331172){return _0x153ab4===_0x331172;},'pWMyY':'number','cViRh':'\x20AND\x20'};if(!_0x516cc2||_0x58c230['apJjq'](typeof _0x516cc2,_0x58c230['MeLBq']))return'';const _0x4081b7=[];for(const [_0x233080,_0x254d57]of Object['entries'](_0x516cc2)){if(typeof _0x254d57===_0x58c230[_0x230e9e(0x22f)])_0x4081b7['push'](''+_0x9c495f+_0x233080+'\x20=\x20\x27'+_0x254d57+'\x27');else{if(_0x58c230['wsjiK'](typeof _0x254d57,'string'))_0x4081b7['push'](''+_0x9c495f+_0x233080+'\x20=\x20\x27'+_0x254d57[_0x230e9e(0x138)](/'/g,'\x27\x27')+'\x27');else _0x58c230[_0x230e9e(0x234)](typeof _0x254d57,_0x58c230[_0x230e9e(0x246)])&&_0x4081b7['push'](''+_0x9c495f+_0x233080+'\x20=\x20'+_0x254d57);}}return _0x4081b7['join'](_0x58c230['cViRh']);}function buildRequestScopeMiddleware(_0x20b163,_0x23cbbf){const _0x1874fc=a0_0x1e54,_0x179f62={'oUAKu':function(_0x41a99d,_0x11bffa){return _0x41a99d||_0x11bffa;}};if(!_0x20b163[_0x1874fc(0x20c)])return'';const {column:_0x404c4a,source:_0x2d58ad,bypassRoles:_0x4fa258}=_0x20b163[_0x1874fc(0x20c)],_0x1b3f53=JSON['stringify'](_0x4fa258||[]),_0x2ea318=_0x179f62['oUAKu'](_0x23cbbf,'')['replace'](/-/g,'_');return'\x0a//\x20───\x20Request\x20Scope\x20Middleware\x20(Layer\x201\x20RLS)\x20──────────────────────────\x0a//\x20Dynamic\x20per-request\x20filtering\x20by\x20\x27'+_0x404c4a+_0x1874fc(0x1b9)+_0x2d58ad+_0x1874fc(0x1ee)+_0x1b3f53+_0x1874fc(0x127)+_0x2d58ad+';\x0a\x20\x20if\x20(scopeValue\x20===\x20undefined\x20||\x20scopeValue\x20===\x20null)\x20{\x0a\x20\x20\x20\x20return\x20res.status(403).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Forbidden\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27Request\x20scope\x20value\x20not\x20available\x20in\x20user\x20context\x27,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a\x0a\x20\x20//\x20Expose\x20scope\x20info\x20for\x20route\x20handlers\x20(ownership\x20verification\x20in\x20/first,\x20/update,\x20/update-composite)\x0a\x20\x20req._requestScope\x20=\x20{\x20column:\x20\x27'+_0x404c4a+'\x27,\x20value:\x20scopeValue\x20};\x0a\x0a\x20\x20const\x20endpoint\x20=\x20req.path.substring(1);\x0a\x20\x20const\x20scopeCondition\x20=\x20{\x20key:\x20\x27'+_0x404c4a+_0x1874fc(0x241)+_0x404c4a+_0x1874fc(0x16f)+_0x404c4a+_0x1874fc(0x15d)+_0x2ea318+'\x27]\x20||\x20req.body;\x0a\x20\x20\x20\x20masterBody[\x27'+_0x404c4a+_0x1874fc(0x210);}function a0_0x2998(){const _0x2ccceb=['ksb8FcaWoWOGicaGicaGihjLDhvYBIbZDw0GkYaOCxr5icOGChjPy2uPoWOGicaGicb9lcaWktSkicaGicaGCMvJywXJrMLLBgrZlNb1C2GOj3rVDgfSx2fTB3vUDca9idONicSGCMvJywXJswr4kYSPoWOGicaGicbYzwnHBgnwywX1zxmUChvZAcH0B3rHBefTB3vUDcK7cIaGicb9','cIaGicaVlYbgywXSyMfJAZOGBw9Kzsb0yw5WysbLDMvUDhmkicaGihrYEsb7cIaGicaGihjLC3bVBNnLrgf0ysa9igf3ywL0ia','cGOGihjLDhvYBIbMB3jTyxr0zwq7cN0k','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','C3bSAxq','oWOGicaGy29UC3qGBw9KzwXjBMzVid0GyxDHAxqG','lNvWzgf0zunVBxbVC2L0zsHKyxrHlcbLDMvUDenVBNrLEhqPoWOkicaGignVBNnVBguUBg9NkcC','AxndueC','iL0GpsbUDwXSoWOGih0','cI8QkGOGkIbdB21WB3nPDguGDxbKyxrLic0GvxbKyxrLigHLywrLCIb3AxrOigDYyw51BgfYigrLDgfPBcbVCgvYyxrPB25ZicHpCMfJBguPcIaQlWPHC3LUyYb1CgrHDgvdB21WB3nPDguOzgf0ysWGzxzLBNrdB250zxH0id0GBNvSBcKGEWOGignVBNn0ignVBM5Ly3rPB24GpsbHD2fPDcbKyI5NzxrdB25Uzwn0Aw9UkcK7cIaGDhj5ihSkicaGignVBNn0ihbYAw1HCNLlzxLwywX1zsa9igrHDgfBDgHPCY5WCMLTyxj5s2v5xtSkicaGigLMicGHChjPBwfYEuTLEvzHBhvLksb7cIaGicaGihrOCM93ig5LDYbfCNjVCIGNuhjPBwfYEsbRzxKGjYaRihrOAxmUChjPBwfYEuTLEsaRicCGAxmGCMvXDwLYzwqGzM9YihvWzgf0zsCPoWOGicaGFqOkicaGic8VienOzwnRigLMihjLy29YzcbLEgLZDhmGkgfSC28GC2vYDMvZigfZihbYzwzLDgnOig9SzerHDgeGzM9YigHVB2TZkqOGicaGy29UC3qGy2HLy2TtCwWGpsaNu0vmrunuicOGrLjptsaNicSGDgHPCY53CML0zvnVDxjJzsaRicCGv0HfuKuGjYaRihrOAxmUChjPBwfYEuTLEsaRicCGpsa6msC7cIaGicbJB25ZDcbJAgvJA1jLC3vSDca9igf3ywL0ignVBM5Ly3rPB24UzxHLy3v0zsHJAgvJA1nXBcWGw3bYAw1HCNLlzxLwywX1zv0SihSGyxv0B0nVBw1PDdOGzMfSC2uSig91DezVCM1HDdOGB3jHy2XLzgiUt1vux0zpuK1bvf9pqKPfq1qGFsK7cIaGicbPzIaOiwnOzwnRuMvZDwX0lNjVD3mGFhWGy2HLy2Tszxn1BhqUCM93CY5Szw5NDgGGpt09idaPihSkicaGicaGDgHYB3CGBMv3ievYCM9YkcDszwnVCMqGBM90igzVDw5KjYK7cIaGicb9cGOGicaGy29UC3qGB2XKrgf0ysa9ihrOAxmUzM9YBwf0uMvZCg9UC2veyxrHkgnOzwnRuMvZDwX0lNjVD3nBmf0PoWOkicaGic8Viev4DhjHy3qGAgvHzgvYigrHDgekicaGignVBNn0igHLywrLCKrHDgeGpsb7ic4UlMrHDgeGFtSkicaGignVBNn0igrLDgfPBeTLEsa9icC','A2v5CW','D1noChu','cGOGihjLDhvYBIb0zw1WBgf0zxm7cN0kcI8QkGOGkIbpDMvYCMLKzsbNzxrmAxn0uxvLCNKGDw50DwSGt3jHy2XLihn5BNrHEaOGkI8kz2v0tgLZDff1zxj5kg9WDgLVBNmGpsb7FsKGEWOGigXLDcbIyxnLuxvLCNKGpsbGcIaGica','igrHDgeGywrQDxn0zwqGC3vJy2vZC2z1BgX5oIa','zgf0zq','cIaGicb2yxiGAgvHzgvYq2fSyYa9ia','DhjPBq','jYbMCM9Tia','cIOGrgf0ywjHC2u6ie9YywnSzqOQlWPJBgfZCYa','BMfTzq','BgvUz3rO','ih0ScIaGicaGicaGicb7igTLEtOGCMvXlL9Yzxf1zxn0u2nVCguUy29SDw1Ulcb2ywX1ztOGCMvXlL9Yzxf1zxn0u2nVCguUDMfSDwuGFqOGicaGicaGif0kicaGicaGFsK7cIaGicaGigLMicGHC2nVCgvdAgvJAY5ZDwnJzxnZihX8icfZy29WzunOzwnRlMrHDgeGFhWGC2nVCgvdAgvJAY5KyxrHlMXLBMD0Aca9pt0GmcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWncKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNrgf0ysbUB3qGzM91BMqNlaOGicaGicaGicaGBwvZC2fNztOGj01HC3rLCIbYzwnVCMqGBM90igzVDw5Kig9YigfJy2vZCYbKzw5PzwqNlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGFqO','lMnOyw5Nzvn0yxr1C0rHDgeOCMvXlMjVzhKSihDVCMTMBg93q29UzMLNlcbLDMvUDenVBNrLEhqPoWOGicaGicbJB25ZB2XLlMXVzYGNw0zbteXcqunlxsbdsefor0uTu1rbvfvtignVBxbSzxrLzcb3AxrOB3v0ignVBxbVBMvUDcbLDMvUDhmNktSkicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGignVBNnVBguUzxjYB3iOj1TgquXmqKfds10Gq0HbtKDflvnuqvrvuYbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','ifn1yM1VzhvSzsaTie9YywnSzsbeyxrHyMfZzqOQieDLBMvYyxrLzdOG','Bg9VA3vW','iL0GpsaI','cIaGxtSkcIaGy29UC3qGzgf0yxrHyMXLC1DOzxjLid0G','Aw5JBhvKzxm','AxnbCNjHEq','ktSkicaGicaGy29UC29Szs5SB2COj1TjtLrfr1jbveveifrsqu5tqunusu9oxsbvuerbveuGy29TCgXLDgvKihn1y2nLC3nMDwXSEsb3AxrOigv2zw50CYCPoWOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNw0LovevhuKfuruqGvfjbtLnbq1rjt05DifvqrefursbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','B2jQzwn0','ANvKDwW','zxHWB3j0CW','igrHDgeGBM90igzVDw5KjYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicbPzIaOzxjYB3iUzxjYB3joDw0Gpt09idePihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda5ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0r1CgXPy2f0zsbLBNrYEsCScIaGicaGicaGBwvZC2fNztOGj0eGCMvJB3jKihDPDgGGDgHPCYb2ywX1zsbHBhjLywr5igv4Axn0CYCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLihvWzgf0Aw5Nia','cI8QkGOQia','DxbSB2fKq29UzMLN','cI8QkGOGkIbdB21WB3nPDguGCMvHzcaTifjLywqGAgvHzgvYihDPDgGGzgv0ywLSigL0zw1ZicHpCMfJBguPcIaQlWPHC3LUyYbYzwfKq29TCg9ZAxrLkg9WDgLVBNmPihSkicb0CNKGEWOGicaGAwyGkcfVChrPB25ZlNDOzxjLksb7cIaGicaGihrOCM93ig5LDYbfCNjVCIGNsw52ywXPzcbYzxf1zxn0igzVCM1HDdOGD2HLCMuGCgfYyw1LDgvYigLZihjLCxvPCMvKjYK7cIaGicb9cGOGicaGBgv0ihDOzxjLq2XHDxnLuMvZDwX0oWOGicaGDhj5ihSkicaGicaGD2HLCMvdBgf1C2vszxn1BhqGpsb0AgLZlMj1AwXKq29TCgXLEfDOzxjLq2XHDxnLkg9WDgLVBNmUD2HLCMuPoWOGicaGFsbJyxrJAcaOzsKGEWOGicaGicbJB25ZDcbLCNjVCIa9ig5LDYbfCNjVCIGNsw52ywXPzcb3AgvYzsbJB25KAxrPB25ZoIaNicSGzs5TzxnZywDLktSkicaGicaGzxjYB3iUC3rHDhvZq29Kzsa9idqWmdSkicaGicaGDgHYB3CGzxjYB3i7cIaGicb9cIaGicbJB25ZDcb7ihnXBdOGD2HLCMvdBgf1C2uSihbHCMfTCYb9id0GD2HLCMvdBgf1C2vszxn1Bhq7cIaGicbJB25ZDcbOzwfKzxjtCwWGpsaNu0vmrunuicOGrLjptsaNicSGDgHPCY5NzxruywjSzvnVDxjJzsGNCMvHzcCPicSGjYbxsevsrsaNicSGD2HLCMvdBgf1C2u7cIaGicbJB25ZDcbOzwfKzxjszxn1BhrZid0GyxDHAxqGzgiUzxHLy3v0zvf1zxj5kgHLywrLCLnXBcWGCgfYyw1ZktSkcIaGicbPzIaOiwHLywrLCLjLC3vSDhmGFhWGAgvHzgvYuMvZDwX0CY5Szw5NDgGGpt09idaPihSkicaGicaGCMv0DxjUihSGC3vJy2vZCZOGDhj1zsWGy291BNq6idaSigrHDge6ifTDih07cIaGicb9cGOGicaGy29UC3qGy29TCg9ZAxrLuMvZDwX0CYa9ifTDoWOGicaGy29UC3qGzgv0ywLSs2v5id0GjW','lMDLDenVBM5Ly3rPB25jBMzVkcK7cGOGicaGCMvZlMPZB24OEWOGicaGicbZDgf0Dxm6ignVBM5Ly3rPB25jBMzVid8Gj2HLywX0AhKNidOGj3vUA25VD24NlaOGicaGicbLBMrWB2LUDdOGjW','cIaGicaVlYbdB21WB25LBNqGzw5NAw5LoIbIDwLSzcbLDMvUDenVBNrLEhqGDw50DwSGBw9KzwWTBgv2zwWGzxzLBNqGBgLMzwn5y2XLcIaGicbPzIaOy29TCg9Uzw50rw5NAw5LicyMienVBNrLEhrcDwLSzgvYksb7cIaGicaGihrYEsb7cIaGicaGicaGy29UC3qGzxzLBNrdB250zxH0id0GEWOGicaGicaGicaGy29TCg9Uzw50rw5NAw5LoIbJB21WB25LBNrfBMDPBMuScIaGicaGicaGicbdB250zxH0qNvPBgrLCJOGq29UDgv4Dej1AwXKzxiScIaGicaGicaGicb0ywjSzu5HBwu6icC','lMfKzerHDgeOCMvXlMjVzhKSihSGywrKAxrPB25HBenVBNrLEhq6ihSGCMvXDwvZDeLKoIbYzxeUAwqGFhWGBNvSBcb9ih0PoWOGicaGicaGignVBNnVBguUBg9NkcDBrKfmtejbq0TDieLou0vsvcbJB21WBgv0zwqGD2L0Ag91DcbLDMvUDhmNktSkicaGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGicbJB25ZB2XLlMvYCM9YkcDBrKfmtejbq0TDieLou0vsvcbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGicaGDgHYB3CGzxjYB3i7cIaGicaGih0kicaGih0k','id0GoJeGt1jerviGqLKGBgLUzv9UDw1IzxiNoW','Dg90ywXFyw1VDw50','AM9PBG','ksbxsevsrsbst1Dovu0Gpd0GmtaWmga7cIaGicbJB25ZDcbKyxrHid0GyxDHAxqGzgiUzxHLy3v0zvf1zxj5khf1zxj5ktSkcIaGicaVlYbdywnOzsbYzxn1BhqGDgfUCgeGC2vSzwn0zwqGzMXHzWOGicaGy29UC3qGy2fJAgveyxrHid0Gzgf0ys5TyxaOAxrLBsa9pIaOEWOGicaGicbPzdOGAxrLBs4','iIbTDxn0igjLigeGBM9UlwvTChr5igfYCMf5jYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGica','y2H1BMTtAxPL','y29UC3rYywLUDhm','AxrLBxmU','D29YA2zSB3C','cI8QkGOGkIbdB21WB3nPDguGy3jLyxrLic0Gq3jLyxrLigHLywrLCIb3AxrOigrLDgfPBcbPDgvTCYbPBIbHihnPBMDSzsb0CMfUC2fJDgLVBIaOt3jHy2XLkqOGkI8kyxn5BMmGy3jLyxrLq29TCg9ZAxrLkgrHDgeSigv2zw50q29UDgv4Dca9ig51BgWPihSkicbJB25ZDcbJB25Uzwn0Aw9Uid0GyxDHAxqGzgiUz2v0q29UBMvJDgLVBIGPoWOGihrYEsb7cIaGicbJB25ZDcbKzxrHAwXlzxKGpsaN','l2zPCNn0ic0GtwvUzgfWyxrRyw4Gzgf0ysbIzxjKyxnHCMTHBIbRCML0zxjPyqPYB3v0zxiUCg9ZDcGNl2zPCNn0jYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGDhj5ihSkicaGic8Vie5VCM1HBgL6ztOGyxjYyxKGmsbLBgvTzw4GW6lIGkdIGjKGB2jQzwn0icHIywnRD2fYzcbJB21WyxrPyMXLkqOGicaGAwyGkefYCMf5lMLZqxjYyxKOCMvXlMjVzhKUD2HLCMuPicyMihjLCs5IB2r5lNDOzxjLlMXLBMD0Aca9pt0GmsKGEWOGicaGicbYzxeUyM9KEs53AgvYzsa9ihjLCs5IB2r5lNDOzxjLwZbDoWOGicaGFqOkicaGic8VifzHBgLKyxnPihDOzxjLignSyxvZzsddOUkcRokaNsbOyxj1CYbVyMPLy3qGDhvUz2DHBcb7A2v5lcb2ywX1zx0kicaGigLMicGHCMvXlMjVzhKUD2HLCMuGFhWGDhLWzw9MihjLCs5IB2r5lNDOzxjLice9psaNB2jQzwn0jYb8FcbbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNtwLZC2LUzYbYzxf1AxjLzcbMAwvSzcCScIaGicaGicaGBwvZC2fNztOGj1bYB3bLCNr5ihDOzxjLigLZihjLCxvPCMvKigfZihTRzxKSihzHBhvLFsbVyMPLy3qNlaOGicaGicaGigv4yw1WBgu6ihSkicaGicaGicaGicj3AgvYzsi6ihSGiMTLEsi6ici','DMLLD05HBwu','l2nYzwf0zs1JB21WB3nPDgu6jYWGsLnptI5ZDhjPBMDPzNKOCMvXlMjVzhKSig51BgWSidiPktSkcIaGicbPzIaOixjLCs5IB2r5ihX8icfYzxeUyM9KEs4','yxLTBui','zM9YzwLNBKTLEq','lMDLDerHDgf0ywjSzxmOB3b0Aw9UCYK7cGOGicaGlY8GtwvUyw1IywHRyw4GBM9TB3iGyMfYAxmGDw50DwSGrgf0yvrHyMXLCWOGicaGAwyGkhjLC3vSDc5KyxrHicyMiefYCMf5lMLZqxjYyxKOCMvZDwX0lMrHDgePksb7cIaGicaGihjLC3vSDc5KyxrHid0GCMvZDwX0lMrHDgeUBwfWkcHPDgvTlcbPBMrLEcKGpt4GkhSkicaGicaGicaUlI5PDgvTlaOGicaGicaGihjVD251BwvYyxrVCJOGB3b0Aw9UCY5ZDgfYDcaRigLUzgv4icSGmqOGicaGicb9ksK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5QC29UkhjLC3vSDcK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUia','lNzHBgLKyxrLrgf0ysa9pt0Gj2z1BMn0Aw9UjYKGEWOGicaGicbJB25ZDcb2ywXPzgf0Aw9Uid0GyxDHAxqG','zgv0ywLSq29UzMLN','BMfTyq','cIaGicbPzIaOC2vHCMnOksb7cIaGicaGihDOzxjLq29UzgL0Aw9UCY5WDxnOkgbvufbfuIG','uhbSyxe','lNvWzgf0zurHDgeOCMvXlMjVzhKSigv2zw50q29UDgv4Da','l3vWzgf0zs1JB21WB3nPDgu6jYWGsLnptI5ZDhjPBMDPzNKOCMvXlMjVzhKSig51BgWSidiPktSkcIaGicbPzIaOixjLCs5IB2r5ihX8icfYzxeUyM9KEs4','oWOkicaGicaGDMfYihzHBgLKyxrPB24GpsbHD2fPDca','sw52ywXPzcbHDwrPDenVBhvTBNmGDMfSDwuGzM9Yia','cIaGicb9ksK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUie9YywnSzsbNzxrmB29RDxbeyxrHrhLUyw1PyZONlcbLCNjVCIK7cIaGicb0AhjVDYbLCNjVCJSkicb9cN0kcI8QkGOGkIbpDMvYCMLKzsbNzxrtDgf0AwnmB29RDxbeyxrHihvUDhvRie9YywnSzqOGkI8kyxn5BMmGz2v0u3rHDgLJtg9VA3vWrgf0ysHZzwXLy3rLzfrHzYKGEWOGihrYEsb7cIaGicaVlYbdAgvJAYbJywnOzsbMAxjZDcaTignHy2HLihrHBNbHihnLBgvJDgvKvgfNigTHCMvUysbKyxrHihnHBwekicaGignVBNn0ignHy2HLt3b0Aw9UCYa9ihSGDhLWztOGj3n0yxrPyYCGFtSkicaGignVBNn0ignHy2HLzfjLC3vSDca9igf3ywL0ihrOAxmUz2v0q2fJAgvKtg9VA3vWkgnHy2HLt3b0Aw9UCYWGj3n0yxrPyYCPoWOGicaGAwyGkgnHy2HLzfjLC3vSDcKGEWOGicaGicaVlYbbChbSEsbZzwXLy3rLzfrHzYb0BYbJywnOzwqGCMvZDwX0cIaGicaGihjLDhvYBIbJywnOzwrszxn1BhqUBwfWkgL0zw0Gpt4GEWOGicaGicaGigLMicHPDgvTlMLKid09psbZzwXLy3rLzfrHzYKGEWOGicaGicaGicaGCMv0DxjUihSGlI4UAxrLBsWGC2vSzwn0zwq6icD0CNvLjYb9oWOGicaGicaGih0kicaGicaGicbYzxr1CM4GEYbPzdOGAxrLBs5PzcWGDgv4DdOGAxrLBs50zxH0ih07cIaGicaGih0PoWOGicaGFqOkicaGignVBNn0ihf1zxj5id0GyfnftevdvcaQiezst00Gkfnftevdvca','m096tKvzyW','mJC1otu0neT0AgnYrW','jYWkicaGicaGDgfIBgu6ig1VzgvSsw5MBY50ywjSzsWkicaGicaGzMLLBgrZoIbTB2rLBeLUzM8UzMLLBgrZlaOGicaGicbXDwvYEvnVDxjJzxm6ig1VzgvSsw5MBY5XDwvYEvnVDxjJzxmScIaGicaGigfJDgLVBNm6igfJDgLVBNmScIaGicaGigrHDgfIyxnLvhLWztOGj29YywnSzsCScIaGicaGigDLBMvYyxrLzdOGjW','iIWGiNzHBhvLiJOGiNLVDxiTAwqTDMfSDwuIih0kicaGicaGicb9laOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGic8VifrVBgfRigzVCM1HDcbHzhzHBMnLzcaOy29UzgL0Aw9UCY9SB2DPyYKkicaGigLMicHYzxeUyM9KEs53AgvYzs5JB25KAxrPB25ZihX8ihjLCs5IB2r5lNDOzxjLlMXVz2LJksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihDOzxjLigzVCM1HDcCScIaGicaGicaGBwvZC2fNztOGj0fKDMfUy2vKihDOzxjLigzVCM1HDcbPCYbUB3qGC3vWCg9YDgvKigLUic9MAxjZDcbLBMrWB2LUDc4GvxnLic9YzwfKigvUzhbVAw50igzVCIbJB21WBgv4ihf1zxjPzxmNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGic8VifzHBgLKyxnPihDOzxjLlMTLEsbHzgeGzgKGDMfSAwrgAwvSzhmkicaGignVBNn0ihzHBgLKrMLLBgrZid0G','cI8VieDLBMvYyxrLzcbMCM9TihjLCxvLC3rty29WzsbJB25MAwD1CMf0Aw9UigLUihbHEwXVywqGsLnptGPYB3v0zxiUDxnLkcHYzxeSihjLCYWGBMv4DcKGpt4GEWOGigLMicHYzxeUBwv0Ag9Kice9psaNue9tvcCPihjLDhvYBIbUzxH0kcK7cGOGic8VifnRAxaGD2HLBIbUBYbHDxrOignVBNrLEhqGkgvUzhbVAw50ihDPDgHVDxqGyxv0AcbTAwrKBgv3yxjLkqOGigLMicGHCMvXlNvZzxiPihjLDhvYBIbUzxH0kcK7cGOGic8VifnRAxaGD2HLBIb1C2vYigHHCYbIExbHC3mGCM9SzqOGignVBNn0ihvZzxjsB2XLCYa9ihjLCs51C2vYlNjVBgvZihX8ifTDoWOGignVBNn0igj5CgfZC1jVBgvZid0G','lMXLBMD0AdSkicaGih0kicaGigLMicHOzwfKzxjdywXJlNrVDgfSx3f0EsaMjIbOzwfKzxjdywXJlNrVDgfSx3f0Es5ZB3vYy2uPihSkicaGicaGDMfYihf0EuzPzwXKid0GAgvHzgvYq2fSyY50B3rHBf9XDhKUC291CMnLlNjLCgXHy2uOj2L0zw1ZlICSicCNktSkicaGicaGzgf0ys50B3rHBf9XDhKGpsbKyxrHlG','jZSkicaGignVBNn0igzRid0GjW','CMvHzenVBxbVC2L0zq','jZSkicaGigLMicGHCMvXlMjVzhLBChjPBwfYEuTLEv0PihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj01PC3nPBMCGCMvXDwLYzwqGzMLLBgqNlaOGicaGicaGig1LC3nHz2u6igbqCMLTyxj5igTLEsaOjhTWCMLTyxj5s2v5FsKGAxmGCMvXDwLYzwqGzM9YigfKANvZDgaScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGlY8GvMfSAwrHC2KGywrQDxn0BwvUDhmGyxjYyxKkicaGigLMicGHCMvXlMjVzhKUywrQDxn0BwvUDhmGFhWGiufYCMf5lMLZqxjYyxKOCMvXlMjVzhKUywrQDxn0BwvUDhmPihX8ihjLCs5IB2r5lMfKANvZDg1LBNrZlMXLBMD0Aca9pt0GmcKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicbTzxnZywDLoIaNywrQDxn0BwvUDhmGyxjYyxKGAxmGCMvXDwLYzwqGyw5Kig11C3qGBM90igjLigvTChr5jYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0k','jZSkicaGignVBNn0igrLDgfPBfbRid0GjW','jZSkicaGigrLBgv0zsbOzwfKzxjeyxrHw2rLDgfPBeTLEv07cIaGicbKzwXLDguGAgvHzgvYrgf0yvT0AgLZlNbYAw1HCNLlzxLDoWOkicaGic8Vic0TlsbiB29RoIbVBKjLzM9YzunVBxbVC2L0zvvWzgf0zsaTls0kicaGigLMicHLDMvUDenVBNrLEhqGjIyGzxzLBNrdB250zxH0lMnVBxbVBMvUDevUz2LUzsKGEWOGicaGicb2yxiGx2nLid0GzxzLBNrdB250zxH0lMnVBxbVBMvUDevUz2LUztSkicaGicaGDMfYif9dqIa9igv2zw50q29UDgv4Dc5dB250zxH0qNvPBgrLCJSkicaGicaGDMfYif9KzxrHAwXpChmGpsbKyxrHw2rLDgfPBeTLEv0GFhWGE307cIaGicaGihzHCIbFyMvMB3jLq3r4id0Gx0nclMj1AwXKq29TCg9ZAxrLvxbKyxrLqMvMB3jLq29UDgv4DcHOzwfKzxjeyxrHlcbVBgreyxrHlcb7cIaGicaGicaGAw5Zzxj0oIbFzgv0ywLSt3bZlMLUC2vYDcb8FcbBxsWkicaGicaGicb1CgrHDgu6if9KzxrHAwXpChmUDxbKyxrLihX8ifTDlaOGicaGicaGigrLBgv0ztOGx2rLDgfPBe9WCY5KzwXLDguGFhWGw10kicaGicaGFsWGEWOGicaGicaGihrHyMXLtMfTztOGjW','zMLLBgrwywXPzgf0Aw9U','Aw1WB3j0q29UzMLN','l3jLywqTy29TCg9ZAxrLoICSiePtt04UC3rYAw5NAwz5khjLCs5IB2r5lcbUDwXSlcaYksK7cGOGicaGAwyGkcfYzxeUyM9KEsb8FcbpyMPLy3qUA2v5CYHYzxeUyM9KEsKUBgvUz3rOid09psaWksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7ihn1y2nLC3m6igzHBhnLlcbLCNjVCJOGj0LUDMfSAwqGCgf5Bg9HzcCSig1LC3nHz2u6icDqyxLSB2fKignHBM5VDcbIzsbLBxb0EsCSihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPih0PoWOGicaGFqOkicaGigLMicGHCMvXlMjVzhKUD2HLCMuPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj01PC3nPBMCGCMvXDwLYzwqGzMLLBgqNlaOGicaGicaGig1LC3nHz2u6icDqCM9Wzxj0Esb3AgvYzsbPCYbYzxf1AxjLzcCScIaGicaGicaGzxHHBxbSztOGEYaID2HLCMuIoIbBEYaIA2v5iJOGiMzPzwXKx25HBwuIlcaIDMfSDwuIoIaIzMLLBgrFDMfSDwuIih1Dih0ScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGAwyGkcfbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLksaMjIaHCMvXlMjVzhKUD2HLCMuUy29UzgL0Aw9UCYKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcb3AgvYzsbMB3jTyxqNlaOGicaGicaGig1LC3nHz2u6icDjBNzHBgLKihDOzxjLigzVCM1HDcCScIaGicaGicaGzxHHBxbSztOGEYaID2HLCMuIoIbBEYaIA2v5iJOGiG','C3rYAw5NAwz5','cGOGicaGlY8Gtg9Nihn1y2nLC3nMDwWGB3bLCMf0Aw9UcIaGicbJB25ZB2XLlMXVzYHG','lMDLDerHDgeOEWOGicaGicaGihDOzxjLoIbBcIaGicaGicaGicb7igTLEtOGChjPBwfYEuTLEsWGDMfSDwu6ihjLCs5IB2r5w3bYAw1HCNLlzxLDih0ScIaGicaGicaGicb7igTLEtOGCMvXlL9Yzxf1zxn0u2nVCguUy29SDw1Ulcb2ywX1ztOGCMvXlL9Yzxf1zxn0u2nVCguUDMfSDwuGFqOGicaGicaGif0kicaGicaGFsK7cIaGicaGigLMicGHC2nVCgvdAgvJAY5ZDwnJzxnZihX8icfZy29WzunOzwnRlMrHDgeGFhWGC2nVCgvdAgvJAY5KyxrHlMXLBMD0Aca9pt0GmcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWncKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNrgf0ysbUB3qGzM91BMqNlaOGicaGicaGicaGBwvZC2fNztOGjW','mtK4odeZm0TOzLnMza','ywrQDxn0q29UzMLN','B3Ppwem','cIaGDhj5ihSkicaGigLMicH0ExbLB2yGiG','laOGigLTCg9YDenVBMzPzZOG','ksb8FcaWoWOGicaGicaGihjLDhvYBIbZDw0GkYaOCxr5icOGChjPy2uPoWOGicaGicb9lcaWktSkicaGih0','zMLLBgroyw1L','lMDLDeXVB2T1CerHDgfeEw5HBwLJkhnLyxjJAcWGzxH0CMfgAwX0zxjZksa6cIaGicaGigf3ywL0ia','q0HQq0e','Bg9VA3vWrMLLBgrZ','jhT3AgvYzvjLC3vSDc5ZCwX9','DKjrBuC','iIWGiNzHBhvLiJOGiNLVDxiTAwqTDMfSDwuIih0ScIaGicaGicaGicaIC2vSzwn0iJOGwYjMAwvSzdeIlcaIzMLLBgqYiL0kicaGicaGicb9laOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGic8VifzHBgLKyxnPihDOzxjLlMTLEsbKyw4GD2HLCMuUDMfSDwukicaGigLMicGHCMvXlMjVzhKUD2HLCMuUA2v5ihX8ihjLCs5IB2r5lNDOzxjLlNzHBhvLid09psb1BMrLzMLUzwqGFhWGCMvXlMjVzhKUD2HLCMuUDMfSDwuGpt09ig51BgWGFhWGCMvXlMjVzhKUD2HLCMuUDMfSDwuGpt09icCNksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihDOzxjLigzVCM1HDcCScIaGicaGicaGBwvZC2fNztOGj1DOzxjLigTLEsbHBMqGDMfSDwuGyxjLihjLCxvPCMvKjYWkicaGicaGicbLEgfTCgXLoIb7cIaGicaGicaGicaID2HLCMuIoIb7icjRzxKIoIaI','icb0AgLZlNzHBgLKyxrPB25dB25MAwCGpsb7cG','jhT0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYL9','y3jLyxrL','ywDNCMvNyxrLq29UzMLN','CMvXDwvZDfnJB3bL','jYWkicaGicaGicaGig9WDgLVBNm6ihjLCs5IB2r5t3b0Aw9UCYb8Fcb7FsWkicaGicaGicaGihjLCxvLC3rjzdOGCMvXlMLKihX8ig51BgWkicaGicaGicb9cIaGicaGih07cIaGicb9cGOGicaGDMfYihjLC3vSDca9igf3ywL0ia','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUBwvZC2fNzsa9pt0Gj0rHDgeGDgLKywSGzgL0zw11A2fUjYb8FcbLCNjVCI5TzxnZywDLlMLUy2X1zgvZkcDUB3qGzM91BMqNksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdqPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNrgf0ysbUB3qGzM91BMqNlaOGicaGicaGig1LC3nHz2u6icC','zM9YBxvSyq','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DwjXDwvYEqOGicaGicaGid8Gj1nftevdvcaQiezst00GkfnftevdvcbIyxnLx3f1zxj5lIOSifjpv05vtsbYBNvTiezst00GkfnftevdvcaQiezst00GkcCGkYbIyxnLuxvLCNKGkYaNksbIyxnLx3f1zxj5icCGkYaOD2HLCMvdBgf1C2vtCwWGFhWGjYCPicSGB3jKzxjdBgf1C2uGkYaNksbIyxnLx3f1zxj5ifDirvjfifjpv05vtsa8psaNicSGzw5KuM93icSGjYKGv0HfuKuGCM51Bsa+icCGkYbVzMzZzxqkicaGicaGica6icDtruXfq1qGkIbguK9nicHtruXfq1qGys4Qlcbst1Dovu0GCM51BsbguK9nicGNicSGyMfZzvf1zxj5icSGjYaNicSGkhDOzxjLq2XHDxnLu3fSihX8icCNksaRig9YzgvYq2XHDxnLicSGjYKGysbxsevsrsbst1Dovu0Gpd0GjYaRigvUzfjVDYaRicCPifDirvjfihjUDw0GpIaNicSGB2zMC2v0oWOGicaGFsbLBhnLihSkicaGicaGlY8GtM9UlxbHz2LUyxnPigrLBMDHBIbZywzLDhKGBgLTAxqkicaGicaGCxvLCNKGpsbUzwvKC1n1yNf1zxj5cIaGicaGicaGpYaNu0vmrunuicOGrLjptsaOu0vmrunuigjHC2vFCxvLCNKUkIWGuK9xtLvnihjUDw0GrLjptsaOu0vmrunuicOGrLjptsaOjYaRigjHC2vrDwvYEsaRicCPigjHC2vFCxvLCNKGjYaRicH3AgvYzunSyxvZzvnXBcb8FcaNjYKGkYbVCMrLCKnSyxvZzsaRicCPigjHC2vFCxvLCNKGv0HfuKuGuK9xtLvnidW9icCGkYbSAw1PDcaRicCPjWOGicaGicaGidOGj1nftevdvcaQiezst00GkfnftevdvcbHlIOSifjpv05vtsbYBNvTiezst00GkcCGkYbIyxnLuxvLCNKGkYaNicCGkYaOD2HLCMvdBgf1C2vtCwWGFhWGjYCPicSGB3jKzxjdBgf1C2uGkYaNksbHifDirvjfifjpv05vtsa8psaNicSGBgLTAxqGkYaNksC7cIaGicb9cGOGicaGy29UC29Szs5SB2COj0XPC3qGu1fmiff1zxj5oICSihf1zxj5ktSkicaGignVBNnVBguUBg9NkcDmAxn0iff1zxj5ifbHCMfTzxrLCNm6jYWGD2HLCMvqyxjHBxmPoWOGicaGy29UC3qGCMf3rgf0ysa9igf3ywL0igrIlMv4zwn1DgvrDwvYEsHXDwvYEsWGD2HLCMvqyxjHBxmUBgvUz3rOid4Gmca/ihDOzxjLugfYyw1ZidOGDw5KzwzPBMvKktSkcIaGicbJB25ZDcbKyxrHid0GCMf3rgf0ysa/ihjHD0rHDgeUBwfWkcHYB3CPid0+ihSkicaGicaGy29UC3qGEYbstLvnlcbYBNvTlcaUlI5JBgvHBLjVDYb9id0GCM93oWOGicaGicbYzxr1CM4GDgHPCY5MB3jTyxrszxnWB25ZzurHDgeOy2XLyw5sB3CPoWOGicaGFsKGoIbBxtSkcIaGicbJB25ZDcbYzxn1BhqGpsb7cIaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGigrHDge6igrHDgekicaGih07cGOGicaGAwyGkhbHz2LUyxrLksb7cIaGicaGignVBNn0ihrVDgfSugfNzxmGpsbnyxrOlMnLAwWODg90ywXszwnVCMrZic8GCgvYugfNzsK7cIaGicaGihjLC3vSDc5WywDPBMf0Aw9Uid0GEWOGicaGicaGign1CNjLBNrFCgfNztOGCgfNzsWkicaGicaGicbWzxjFCgfNztOGCgvYugfNzsWkicaGicaGicb0B3rHBf9YzwnVCMrZoIb0B3rHBfjLy29YzhmScIaGicaGicaGDg90ywXFCgfNzxm6ihrVDgfSugfNzxmScIaGicaGicaGAgfZx25LEhq6ihbHz2uGpcb0B3rHBfbHz2vZlaOGicaGicaGigHHC19WCMv2Aw91CZOGCgfNzsa+idekicaGicaGFtSkicaGih0kcIaGicaVlYbdywnOzsbYzxn1BhqkicaGigf3ywL0ihrOAxmUC2v0q2fJAgvKtgLZDcHVChrPB25ZlcbYzxn1BhqPoWOGicaGy29UC29Szs5SB2COyfTdywnOzv0Gu0vuigzVCIbSAxn0ic0GjhTJywnOzuLUzM99ycK7cGOGicaGCMv0DxjUihjLC3vSDdSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGAw4Gz2v0tgLZDdONlcbLCNjVCIK7cIaGicb0AhjVDYbLCNjVCJSkicb9cN0kcI8QkGOGkIbpDMvYCMLKzsbNzxrmB29RDxbeyxrHihvUDhvRie9YywnSzsaOzhLUyw1PyYbZzwfYy2GPcIaQlWPHC3LUyYbNzxrmB29RDxbeyxrHkhnLyxjJAcKGEWOGihrYEsb7cIaGicbJB25ZDcbXDwvYEsa9igbtruXfq1qGkIbguK9nicHtruXfq1qG','ig5VDcbMB3vUzdOGjhTMAwXLugf0Ah1GktSkicaGicaGicb0zw1WBgf0zxnBiG','zMLLBgrqB2XPy3K','ywn0Aw9U','CMvXDwLYzwrgAwvSzhm','Aw1WB3j0','lMXLBMD0Aca9pt0GmcKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicbTzxnZywDLoIaNuhjVCgvYDhKGiG','igrHDgeGC3vJy2vZC2z1BgX5ignYzwf0zwqGkhDPDgGGzgv0ywLSigL0zw1ZksCScIaGicaGigrHDge6ihjLC3vSDcWkicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGih0PoWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkcDfCNjVCIbZywf0ignVBxbVC2L0zsbJCMvHDguG','ktSkicaGicaGy29UC29Szs5SB2COj1TgquXmqKfds10GqurkvvnuignVBxbSzxrLzcb3AxrOB3v0igv2zw50CYCPoWOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNw0zbteXcqunlxsbbrePvu1qGzMfPBgvKoICSigvYCM9YlM1LC3nHz2uPoWOGicaGicb0AhjVDYbLCNjVCJSkicaGih0k','jYWkicbMAwvSze5HBwu6ia','zgf0yxrHyMXLC1DOzxjL','psr7CMvXlMjVzhLBjW','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUzxjYB3joDw0Gpt09idePihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda5ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0r1CgXPy2f0zsbLBNrYEsCScIaGicaGicaGBwvZC2fNztOGj0eGCMvJB3jKihDPDgGGDgHPCYb2ywX1zsbHBhjLywr5igv4Axn0CYCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGAwyGkgvYCM9YlMvYCM9YtNvTid09psaYmJKXksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDgB3jLAwDUigTLEsbJB25ZDhjHAw50jYWkicaGicaGicbTzxnZywDLoIaNuMvMzxjLBMnLzcbKyxrHig5VDcbMB3vUzcCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigfKzgLUzYa','laOGigfKANvZDenVBMzPzZOG','jWOGih07cGOGihrOAxmUywr2yw5JzwrrDwvYEvrLBxbSyxrLCYa9ihrOAxmUBg9HzefKDMfUy2vKuxvLCNLuzw1WBgf0zxmOktSkFqOklYOQcIaQieXVywqGywr2yw5JzwqGCxvLCNKGDgvTCgXHDgvZigrHCMKGzMLSzqOGkI8kBg9HzefKDMfUy2vKuxvLCNLuzw1WBgf0zxmOksb7cIaGy29UC3qGDgvTCgXHDgvZid0GE307cGOGia','yxv0B0nHBgn1Bgf0zuzPzwXKCW','Dg9vChbLCKnHC2u','Dg9mB3DLCKnHC2u','CMvHza','jYWGjW','DxbKyxrLx2v4Axn0Aw5N','zM9YBwf0DgvKlG','Dg9ju09tDhjPBMC','oMaSigvYCM9YktSkicaGihrLBxbSyxrLC1SI','cGOGicaGy29UC29Szs5SB2COya','ieforcbGicSGD2HLCMvdBgf1C2vtCwWUCMvWBgfJzsGVxLDirvjfxhmRl2KSicCNktSkicaGih0GzwXZzsb7cIaGicaGihDOzxjLq2XHDxnLu3fSid0Gj1Dirvjfia','oWOGicaGD29YA2zSB3DdB25MAwCUx3bYB2PLy3qGpsaN','se5jree','zgvMyxvSDfnJB3bL','jZSkicaGia','jYWkicaGicaGicaGigzVCMvPz25lzxK6icC','uffdtwK','tKHVD1u','jZSkicaGigXLDcb0zxH0rMLLBgqGpsaN','DgHPCY5HDwrPDenVBhvTBNmGpsa','v1LNtwC','ie9srevsiejzia','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','DgHPCY5HDwrPDenVBhvTBNmGpsbUDwXSoWO','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','q0DZtgK','lMrLBgv0zurHDgeOCMvXlMjVzhKSigv2zw50q29UDgv4DcK7cIaGicaGignVBNnVBguUBg9NkcDBsu5uruDsqvrfrcbuuKfou0fdveLptL0GrevmrvrfignVBxbSzxrLzcbZDwnJzxnZzNvSBhKGD2L0AcbLDMvUDhmNktSkicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGignVBNnVBguUzxjYB3iOj1TjtLrfr1jbveveifrsqu5tqunusu9oxsberuXfveuGzMfPBgvKoICSigvYCM9YlM1LC3nHz2uPoWOGicaGicb0AhjVDYbLCNjVCJSkicaGih0k','y3jLyxrLq29TCg9ZAxrL','lMnOyw5Nzvn0yxr1C0rHDgeOCMvXlMjVzhKSihDVCMTMBg93q29UzMLNlcbLDMvUDenVBNrLEhqPoWOGicaGicbJB25ZB2XLlMXVzYGNw0LovevhuKfuruqGvfjbtLnbq1rjt05Dieniqu5hrs1tvefuvvmGy29TCgXLDgvKihn1y2nLC3nMDwXSEsb3AxrOigv2zw50CYCPoWOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNw0LovevhuKfuruqGvfjbtLnbq1rjt05Dieniqu5hrs1tvefuvvmGzMfPBgvKoICSigvYCM9YlM1LC3nHz2uPoWOGicaGicb0AhjVDYbLCNjVCJSkicaGih0k','CfDnEvK','zMLYC3q','cIaGicaVlYbjBNrLz3jHDgvKihrYyw5Zywn0Aw9UigrLBMDHBIbLDMvUDcbSAwzLy3LJBgukicaGihrYEsb7cIaGicaGignVBNn0igv2zw50q29UDgv4Dca9ihSkicaGicaGicbJB21WB25LBNrfBMDPBMu6ignVBxbVBMvUDevUz2LUzsWkicaGicaGicbdB250zxH0qNvPBgrLCJOGq29UDgv4Dej1AwXKzxiScIaGicaGicaGDgfIBgvoyw1LoIaN','DgL0Bgu','ktSkicaGicaGy29UC29Szs5SB2COj1TjtLrfr1jbveveifrsqu5tqunusu9oxsbbrePvu1qGy29TCgXLDgvKihn1y2nLC3nMDwXSEsb3AxrOigv2zw50CYCPoWOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNw0LovevhuKfuruqGvfjbtLnbq1rjt05DiefesLvtvcbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','cIaGicaGicaGAwyGkhzHBhvLice9psb1BMrLzMLUzwqGjIyGDMfSDwuGit09ig51BgWPihSkicaGicaGicaGigrgAwvSzhmUChvZAcHRzxKGkYaNid0GoICGkYbKswr4kYSPoWOGicaGicaGicaGzfzHBhvLCY5WDxnOkhzHBhvLktSkicaGicaGicb9cIaGicaGih0kcIaGicaGic8VieLUAMvJDcbHDwrPDcbJB2X1Bw5ZicH1CgrHDgvKx2f0lcb1CgrHDgvKx2j5ksbRzsbKzxrHAwWGvvbeqvrfihzPysbOzwXWzxikicaGicaGzeLKEca9ihrOAxmUx2fWCgvUzfvWzgf0zuf1zgL0q29SDw1UCYHKrMLLBgrZlcbKvMfSDwvZlcbPDgvTlcbLDMvUDenVBNrLEhqSigrjzhGPoWOkicaGicaGzfzHBhvLCY5WDxnOkgL0zw1Bzgv0ywLSugTDktSkicaGicaGy29UC3qGzfvWzgf0zvnXBca9icDvuerbveuGjYaRigrLDgfPBfrHyMXLrNvSBcaRicCGu0vuicCGkYbKrMLLBgrZlMPVAw4OjYWGjYKGkYaNifDirvjficCGkYbKzxrHAwXqAYaRicCGpsa6jYaRigrjzhG7cIaGicaGigf3ywL0ignVBM5Ly3rPB24UzxHLy3v0zsHKvxbKyxrLu3fSlcbKvMfSDwvZlcb7igf1Dg9dB21TAxq6igzHBhnLih0PoWOGicaGicb1CgrHDgvKsxrLBxmUChvZAcHPDgvTktSkicaGih0kicaGignVBNnVBguUBg9NkcDvCgrHDgvKicCGkYb1CgrHDgvKsxrLBxmUBgvUz3rOicSGjYbKzxrHAwWGAxrLBsHZksCPoWOkicaGic8VidmUieLou0vsvcbVCgvYyxrPB25ZcIaGicbMB3iGkgnVBNn0igL0zw0GB2yGAw5Zzxj0sxrLBxmPihSkicaGicaGAxrLBvTMA10GpsbWCMLTyxj5s2v5vMfSDwu7cGOGicaGicaVlYbbDxrVlwDLBMvYyxrLifvvsuqGDw50DwSGzgv0ywLSifbligjPBgeGy2XPzw50ihrPzgfRig1LBNL1ChbSEqOGicaGicaVlYaOA29UC2LZDgvUigrLBMDHBIbJCMvHDgvdB21WB3nPDguGt3jHy2XLkqOGicaGicbPzIaOAxrLBvTKzxrHAwXqA10Gpt09ihvUzgvMAw5Lzcb8FcbPDgvTw2rLDgfPBfbRxsa9pt0GBNvSBcKGEWOGicaGicaGigL0zw1Bzgv0ywLSugTDid0GCMvXDwLYzsGNDxvPzcCPlNy3kcK7cIaGicaGih0k','cIOGuhjPBwfYEsblzxK6ia','l2nYzwf0zs1JB21WB3nPDguGlsbpCMfJBguGq29TCg9ZAxrLignYzwf0zsaOBwfZDgvYlwrLDgfPBcKkCM91DgvYlNbVC3qOjY9JCMvHDguTy29TCg9ZAxrLjYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGDhj5ihSkicaGignVBNnVBguUBg9NkcDszxf1zxn0igjVzhKG','zgvSzxrL','cIaGicaGignVBNn0igrgAwvSzhmGpsbBxtSkicaGicaGy29UC3qGzfzHBhvLCYa9ifTDoWOGicaGicbJB25ZDcbKugXHy2vOB2XKzxjZid0Gw107cIaGicaGigXLDcbKswr4id0GmtSkicaGicaGzM9YicHJB25ZDcbBA2v5lcb2ywX1zv0GB2yGt2jQzwn0lMvUDhjPzxmOAxrLBsKPihSkicaGicaGica','D2rRALC','l2HLywX0AcCSicHYzxeSihjLCYKGpt4GEWOGicaGicbJB25ZDcbOzwfSDgHjBMzVid0GEWOGicaGicaGihn0yxr1CZOGj29RjYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOks5YzxbSywnLkcDujYWGjYaNks5YzxbSywnLkc9ClLXKEZn9wIqVlcaNjYKScIaGicaGicaGC2vYDMLJztOGjW','DgLTzq','lNzHBgLKyxrLrgf0ysa9pt0Gj2z1BMn0Aw9UjYKGEWOGicaGicb2yxiGAgvHzgvYrgf0yuzVCLzHBgLKyxrPB24GpsbpyMPLy3qUyxnZAwDUkhT9lcbKyxrHktSkicaGicaGzgvSzxrLigHLywrLCKrHDgfgB3jwywXPzgf0Aw9UlG','zgf0yxrHyMXLCW','DgfIBgvoyw1L','y3jLyxrLzej5','ksbPCYbYzxf1AxjLzcbMB3iGDxbKyxrLjYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0k','u1vsqM0','iezst00GjhT0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYL9ifDirvjfifvquevska','cIaGicb9ksK7cGOGicaGCMv0DxjUihjLC3vSDdSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGAw4Gt3jHy2XLigDLDeXVB2T1CerHDge6jYWGzxjYB3iPoWOGicaGDgHYB3CGzxjYB3i7cIaGFqP9cGOVkIOkicOGrhLUyw1PyYbSB29RDxaGzgvUz2fUigv4DhjHigzPBhrLCNmGDw50DwSGt3jHy2XLcIaQlWPHC3LUyYbNzxrmB29RDxbeyxrHrhLUyw1PyYHZzwfYy2GSigv4DhjHrMLSDgvYCYa9ihT9ksb7cIaGDhj5ihSkicaGigXLDcbWyxjHBxmGpsbBxtSkicaGigXLDcbWyxjHBuLUzgv4id0GmtSkicaGigXLDcb3AgvYzunVBMrPDgLVBNmGpsbBxtSk','jZSkicb0AgLZlNjLywrtB3vYy2uGpsaN','v0HfuKuG','BwfW','jZSkicaGigXLDcbYzxn1BhqGpsbUDwXSoWOk','yLn1see','igrHDgeNlaOGicaGicbKzxrHAwXZoIbLCNjVCI5TzxnZywDLlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqP9ktSkcG','jYWGDMfSDwu6igrHDgeU','oWOGigLMicHIExbHC3nsB2XLCY5Szw5NDgGGpIaWicyMigj5CgfZC1jVBgvZlNnVBwuOCM9Szsa9pIb1C2vYuM9SzxmUAw5JBhvKzxmOCM9SzsKPksb7cIaGicbYzxr1CM4GBMv4DcGPoWOGih0kcIaGlY8GuMvHzcbZy29Wzsb2ywX1zsbMCM9TihvZzxiGy29UDgv4DaOGignVBNn0ihnJB3bLvMfSDwuGpsa','igrHDgf0ywjSzxm6jYWGzxjYB3iPoWOGicaGy29UC3qGC3rHDhvZq29Kzsa9igvYCM9YlNn0yxr1C0nVzguGFhWGntaWoWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOC3rHDhvZq29KzsKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGC3rHDhvZq29Kzsa9pt0GndaWid8Gj0jHzcbszxf1zxn0jYa6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIbZDgf0DxndB2rLid09psa0mdaGpYbLCNjVCI5TzxnZywDLidOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigzLDgnOAw5Nia','y29UC3qGzgv0ywLSu3fSid0Gj1nftevdvcaQiezst00G','A29Kzq','DhLWzq','id0G','AwyGkgDLBMvYyxrLzezPzwXKCY5PBMnSDwrLCYHRzxKPksbJB250Aw51ztS','zw50CMLLCW','DwPusKC','yvzvvxG','l3vWzgf0zs1JB21WB3nPDguGlsbpCMfJBguGq29TCg9ZAxrLihvWzgf0zsaOBwfZDgvYlwrLDgfPBcKkCM91DgvYlNbVC3qOjY91CgrHDguTy29TCg9ZAxrLjYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGDhj5ihSkicaGignVBNnVBguUBg9NkcDszxf1zxn0igjVzhKG','DxbZzxj0s2v5CW','l3vWzgf0zsaTie9YywnSzsbvCgrHDgukCM91DgvYlNbVC3qOjY91CgrHDguNlcbHC3LUyYaOCMvXlcbYzxmPid0+ihSkicb0CNKGEWOGicaGlY8GvMfSAwrHC2KGCgf5Bg9HzaOGicaGAwyGkcfYzxeUyM9KEsb8FcbpyMPLy3qUA2v5CYHYzxeUyM9KEsKUBgvUz3rOid09psaWksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihbHEwXVywqNlaOGicaGicaGig1LC3nHz2u6icDqyxLSB2fKignHBM5VDcbIzsbLBxb0EsCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGlY8GvMfSAwrHC2KGChjPBwfYEsbRzxKkicaGignVBNn0ihbYAw1HCNLlzxKGpsaN','ie1VzgvSic0Gt3jHy2XLierHDgfIyxnLcIOGr2vUzxjHDgvKoIa','cIaGicaVlYbszwnHBgn1Bgf0zsbOzwfKzxiGDg90ywXZcIaGicbJB25ZDcbJywXJDwXHDgLVBNmGpsa','oMaSigvYCM9YktSkicaGicaGicaGihrOCM93igvYCM9YoWOGicaGicaGih0kicaGicaGFqOkicaGicaGlY8GuMvNAxn0zxiGzxHWB3j0ignSzwfUDxaGCM91DgukicaGicaGDhj5ihSkicaGicaGicbfEhbVCNriyw5KBgvYlNjLz2LZDgvYq2XLyw51CfjVDxrLkgfWCcK7cIaGicaGih0Gy2f0y2GGkgnSzwfUDxbfCNjVCIKGEWOGicaGicaGignVBNnVBguUzxjYB3iOj0v4Cg9YDcbJBgvHBNvWihjVDxrLihjLz2LZDhjHDgLVBIbMywLSzwq6jYWGy2XLyw51CevYCM9YlM1LC3nHz2uPoWOGicaGicb9cGOGicaGicbHChaUz2v0kcCVjYWGkhjLCsWGCMvZksa9pIb7cIaGicaGicaGCMvZlMPZB24OEWOGicaGicaGicaGBwvZC2fNztOGjW','igrHDgeGDxbKyxrLzcbZDwnJzxnZzNvSBhK6ia','CMvWBgfJzq','lMDLDerHDgeOEWOGicaGicaGihDOzxjLoIbBcIaGicaGicaGicb7igTLEtOGjW','jYWkicaGicaGicbHzgrPDgLVBMfSq29UDgv4DdOGEWOGicaGicaGicaGDxnLCL9PzdOGCMvXlMHLywrLCNnBj3vZzxiTAwqNxsb8FcbYzxeUAgvHzgvYC1SNEc11C2vYlwLKj10GFhWGj3n5C3rLBsCScIaGicaGicaGicbVChrPB25ZoIbYzxeUyM9KEu9WDgLVBNmGFhWGE30kicaGicaGicb9cIaGicaGih07cIaGicaGihjLC3bVBNnLrgf0ysa9igf3ywL0ia','y29TCg9Uzw50CW','q3DkCw4','lY8Gv0fstKLorZOGAgvHzgvYq2fSy3vSyxrPB25ZlNrVDgfSx2fTB3vUDcbZA2LWCgvKimoI4OkS4OcDig5Vihf0EsbMAwvSzcbJB25MAwD1CMvK','zMLSDgvY','zgv0ywLSvgfIBgu','ktSkicaGicaGy29UC29Szs5SB2COj1TgquXmqKfds10GvvbeqvrfignVBxbSzxrLzcb3AxrOB3v0igv2zw50CYCPoWOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNw0zbteXcqunlxsbvuerbveuGzMfPBgvKoICSigvYCM9YlM1LC3nHz2uPoWOGicaGicb0AhjVDYbLCNjVCJSkicaGih0k','DxbKyxrLzef0','EvvfDfy','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','lY8Gue9tvcaVyxbPlW','zgv0ywLSuxvLCNK','thPgwfG','l2XVB2T1CcaTie9YywnSzsbeEw5HBwLJieXVB2T1CaPYB3v0zxiUz2v0kcCVBg9VA3vWjYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGy29UC3qGB3jHuMvXDwvZDeLKid0GCMvXlM9YyvjLCxvLC3rjzdSkcIaGDhj5ihSkicaGignVBNn0ihjLCxvLC3rnB2rLid0GCMvXlMHLywrLCNnBj3GTCMvXDwvZDc1TB2rLj107cGOGicaGAwyGkhjLCxvLC3rnB2rLice9psaNzhLUyw1PyYCPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0LUDMfSAwqGuMvXDwvZDcbnB2rLjYWkicaGicaGicbTzxnZywDLoIaNwc1szxf1zxn0lu1VzguGAgvHzgvYig11C3qGyMuGC2v0ihrVigr5BMfTAwmNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGigXLDcbZzwfYy2GGpsbYzxeUCxvLCNKUC2vHCMnOihX8icCNoWOGicaGAwyGkefYCMf5lMLZqxjYyxKOC2vHCMnOksKGEWOGicaGicbZzwfYy2GGpsbZzwfYy2HBmf0GFhWGjYC7cIaGicb9cGOGicaGlY8Gu2vHCMnOigXLBMD0Acb2ywXPzgf0Aw9UcIaGicbPzIaOC2vHCMnOlMXLBMD0Aca+ideWmcKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNu2vHCMnOifrVBYbmB25NjYWkicaGicaGicbTzxnZywDLoIaNu2vHCMnOihbHCMfTzxrLCIbTDxn0ig5VDcbLEgnLzwqGmtaWignOyxjHy3rLCNmNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGignVBNnVBguUBg9NkgbBt1jbluXluf0GjhTVCMfszxf1zxn0swr9igr5BMfTAwmGC2vHCMnOoIaKE3nLyxjJAh1GktSkcIaGicaVlYbdB2XSzwn0igv4DhjHigzPBhrLCNmGzgfYAsbXDwvYEsbWyxjHBxmkicaGignVBNn0igv4DhjHrMLSDgvYCYa9ihT9oWOGicaGzM9YicHJB25ZDcbBA2v5lcb2ywX1zv0GB2yGt2jQzwn0lMvUDhjPzxmOCMvXlNf1zxj5ksKGEWOGicaGicbPzIaOA2v5ice9psaNC2vHCMnOjYaMjIa','ifDirvjfia','lMfKzerHDgeOCMvXlMjVzhKSigv2zw50q29UDgv4DcK7cIaGicaGicaGy29UC29Szs5SB2COj1TjtLrfr1jbveveifrsqu5tqunusu9oxsbjtLnfuLqGy29TCgXLDgvKihn1y2nLC3nMDwXSEsb3AxrOigv2zw50CYCPoWOGicaGicb9ignHDgnOicHLCNjVCIKGEWOGicaGicaGignVBNnVBguUzxjYB3iOj1TjtLrfr1jbveveifrsqu5tqunusu9oxsbjtLnfuLqGzMfPBgvKoICSigvYCM9YlM1LC3nHz2uPoWOGicaGicaGihrOCM93igvYCM9YoWOGicaGicb9cIaGicb9igvSC2uGEWOGicaGicb0CNKGEWOGicaGicaGihzHCIbYzxn1BhqGpsbHD2fPDca','C291CMnL','Dg90ywXFCxr5','jYK7cG','oICSigvYCM9YktSkicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUDgvYBMfSifnLCNzLCIbfCNjVCICScIaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcb3AgLSzsbMzxrJAgLUzYa','oICSigvYCM9YktSkicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUDgvYBMfSifnLCNzLCIbfCNjVCICScIaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcb3AgLSzsbYzwfKAw5Nia','zMLLBgrZ','y29UC3qGzxHWCMvZCYa9ihjLCxvPCMuOj2v4ChjLC3mNktSky29UC3qGCM91DgvYid0GzxHWCMvZCY5sB3v0zxiOktSky29UC3qG','zxHJBhvKzuzYB21jBxbVCNq','lY8Gq3vZDg9Tigf1zgL0ignVBhvTBNmGBwfWCgLUzYbKyxjPihbHEwXVywqk','iezst00G','C2XPy2u','ueLTuM4','cIaGyc50CMLTkcK7cGOGic8VienVBNzLCNqGug9ZDgDYzvnrtcbZEw50yxGGDg8Gt3jHy2XLigLMig5LzwrLzaOGigjHC2vrDwvYEsa9ihrOAxmUy29UDMvYDfrVt3jHy2XLu1fmkgjHC2vrDwvYEsK7cGOGihjLDhvYBIbIyxnLuxvLCNK7cN0kcI8QkGOGkIbpDMvYCMLKzsbNzxrszwfKuxvLCNKGDw50DwSGzw5KCg9PBNqGl3jLywqkicOGuhjPB3jPDgfZoIb2Awv3tMfTzsddOUkaOokaMsb2Awv3uxvLCNKGW6lIGkdIGjKGDgfIBgvoyw1LicHtruXfq1qGkIbguK9nihjLywrtB3vYy2uPcIaQlWPNzxrszwfKuxvLCNKOB3b0Aw9UCYa9ihT9ksb7cIaGAwyGkhrOAxmUDMLLD05HBwuGjIyGDgHPCY52Awv3tMfTzsaHpt0GDgHPCY50ywjSzsKGEWOGicaGCMv0DxjUicDtruXfq1qGkIbguK9nicCGkYb0AgLZlNzPzxDoyw1LoWOGih0k','lMfKANvZDerHDgeOCMvXlMjVzhKSigfKANvZDenVBMzPzYWGEYbHzgrPDgLVBMfSq29UDgv4DdOGEYbYzxf1zxn0swq6ihjLCs5Pzcb8FcbUDwXSih0GFq','zgvZy3jPChrPB24','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','zMLSztO','j10GpsbZy29WzvzHBhvLoWOGih0kcIaGlY8Gq1jfqvrflunptvbpu0LursaVifvqrefurs1dt01qt1njveu6igzVCMnLihnJB3bLignVBhvTBIbPBNnPzguGBwfZDgvYigjVzhKUcIaGAwyGkgvUzhbVAw50id09psaNy3jLyxrLlwnVBxbVC2L0zsCGFhWGzw5KCg9PBNqGpt09icD1CgrHDguTy29TCg9ZAxrLjYKGEWOGicaGy29UC3qGBwfZDgvYqM9KEsa9ihjLCs5IB2r5wYC','zxHWB3j0uxvLCNK','nda5ogf6Euz3CG','cIOGrgf0ywjHC2u6ie9YywnSzqOQlWOklY8GuhjPBwfYEsbRzxKGDw50DwSGzw5KCg9PBNqGAw5PcMnVBNn0ihbYAw1HCNLlzxKGpsaN','jYWkicaGicaGicbKzxrHAwXuywjSztOGjW','oWOGicaGAwyGkgHLywrLCKnHBgmUDg90ywXFAxrLBxmPihSkicaGicaGzgf0ys50B3rHBf9PDgvTCYa9igrHDgeU','lMDLDerHDgeOEWOGicaGicaGicaGD2HLCMu6ifT7igTLEtOGzMLYC3rdB25KAxrPB24UA2v5lcb2ywX1ztOGzMLYC3rdB25KAxrPB24UDMfSDwuGFv0kicaGicaGicb9ktSkcIaGicaGicaGAwyGkcfLEgLZDgLUz0rHDgeUC3vJy2vZCYb8FcaHzxHPC3rPBMDeyxrHlMrHDgeGFhWGzxHPC3rPBMDeyxrHlMrHDgeUBgvUz3rOid09psaWksb7cIaGicaGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdqPlMPZB24OEWOGicaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGicaGzxjYB3i6icDeyxrHig5VDcbMB3vUzcCScIaGicaGicaGicaGig1LC3nHz2u6icC','iIWGiNzHBhvLiJOGiNLVDxiTDMfSDwuIih1DcIaGicaGicaGicb9laOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIHGrxjYB3iGDMfSAwrHDgLUzYbpCMfJBguGCgf5Bg9HzcbMB3iGjhTYzxeUCgf0Ah06ycWGzxjYB3iPoWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicbTzxnZywDLoIaNsw52ywXPzcbWyxLSB2fKigzVCM1HDcCScIaGicaGicaGzgv0ywLSCZOGzxjYB3iUBwvZC2fNzsWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kicb9cIaGBMv4DcGPoWP9ktSkcG','su1Xtxu','DxbKyxrLq29TCg9ZAxrL','laOGicaGicaGihrLEhq6igL0zw0U','jYWGzw5KCg9PBNroyw1LlcbPBxbVCNrdB25MAwCPoWOGicaGicaGicaGicaGigXVz2DLCI5PBMzVkhSGzxzLBNq6icDPBxbVCNrFCM91DgvZx3jLz2LZDgvYzwqNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1Lih0SigbjBxbVCNqGCM91DgvZihjLz2LZDgvYzwqGzM9Yicr7zw5KCg9PBNroyw1LFwaPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9ignHDgnOicHPBxbVCNrfCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj2LTCg9YDf9YzwDPC3rYyxrPB25FzxjYB3iNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbLCNjVCJOGAw1WB3j0rxjYB3iUBwvZC2fNzsb9lcbGsw1WB3j0ihjLz2LZDhjHDgLVBIbMywLSzwqGzM9Yicr7zw5KCg9PBNroyw1LFtOGjhTPBxbVCNrfCNjVCI5TzxnZywDLFwaPoWOGicaGicaGicaGFqOkicaGicaGicaGic8VifjLz2LZDgvYihvWBg9HzcbYB3v0zxmGDMLHignLBNrYywXPEMvKigHHBMrSzxikicaGicaGicaGihrYEsb7cIaGicaGicaGicaGignVBNn0ihvWBg9HzenVBMzPzYa9igv4DhjHy3rvCgXVywrdB25MAwDgCM9Trw5KCg9PBNqOzw5KCg9PBNrqyxrOktSkicaGicaGicaGicaGAwyGkhvWBg9HzenVBMzPzYKGEWOGicaGicaGicaGicaGifvWBg9HzeHHBMrSzxiUCMvNAxn0zxjsB3v0zxmOyxbWlcaN','lMfKANvZDerHDgeOCMvXlMjVzhKSigfKANvZDenVBMzPzYWGzxzLBNrdB250zxH0','jYWkicaGicaGBw9KDwXLoIaN','jYWkicaGicaGicbWCMLTyxj5s2v5oIb0AgLZlNbYAw1HCNLlzxKScIaGicaGicaGlI4Ukgv2zw50q29UDgv4Dc5HzgrPDgLVBMfSq29UDgv4Dcb8Fcb7FsKkicaGicaGFsK7cIaGicaGihzHCIbFyMvMB3jLuMvZDwX0id0GyxDHAxqGx2nLlMv4zwn1DgvpBKjLzM9YzunVBxbVC2L0zsGNDxbKyxrLjYWGx2jLzM9Yzun0EcK7cIaGicaGigLMicGHx2jLzM9YzvjLC3vSDc5ZDwnJzxnZksb7cIaGicaGicaGyxDHAxqGy29UBMvJDgLVBI5YB2XSyMfJAYGPoWOGicaGicaGihrOCM93ig5LDYbfCNjVCIGNB25czwzVCMvdB21WB3nPDgvvCgrHDguGzMfPBgvKoIaNicSGx2jLzM9YzvjLC3vSDc5LCNjVCIK7cIaGicaGih0kicaGih0kcIaGicaVlYbcDwLSzcbOzwfKzxiGvvbeqvrfcIaGicbJB25ZDcbOzwfKzxjgAwvSzhmGpsbBxtSkicaGignVBNn0igHLywrLCLzHBhvLCYa9ifTDoWOGicaGBgv0igLKEca9ide7cIaGicbMB3iGkgnVBNn0ifTRzxKSihzHBhvLxsbVzIbpyMPLy3qUzw50CMLLCYHOzwfKzxjeyxrHksKGEWOGicaGicbPzIaODMfSDwuGit09ihvUzgvMAw5LzcaMjIb2ywX1zsaHpt0GBNvSBcKGEWOGicaGicaGigHLywrLCKzPzwXKCY5WDxnOkgTLEsaRicCGpsa6jYaRigLKEcSRktSkicaGicaGicbOzwfKzxjwywX1zxmUChvZAcH2ywX1zsK7cIaGicaGih0kicaGih0kcIaGicaVlYbjBMPLy3qGyxvKAxqGy29SDw1UCYaODxbKyxrLzf9HDcWGDxbKyxrLzf9IEsKGDMLHigHLBhbLCGOGicaGAwr4id0GDgHPCY5FyxbWzw5KvxbKyxrLqxvKAxrdB2X1Bw5ZkgHLywrLCKzPzwXKCYWGAgvHzgvYvMfSDwvZlcbOzwfKzxjeyxrHlcbLDMvUDenVBNrLEhqSigLKEcK7cGOGicaGAgvHzgvYvMfSDwvZlNb1C2GOChjPBwfYEuTLEvzHBhvLktSkicaGignVBNn0ihvWzgf0zvnXBca9icDvuerbveuGjYaRihrOAxmUD3jPDgvtB3vYy2uGkYaNifnfvcaNicSGAgvHzgvYrMLLBgrZlMPVAw4OjYWGjYKGkYaNifDirvjficCGkYb0AgLZlNbYAw1HCNLlzxKGkYaNid0GoICGkYbPzhG7cIaGicbJB25ZB2XLlMXVzYGNrxHLy3v0Aw5NigHLywrLCIbvuerbveu6jYWGEYbXDwvYEtOGDxbKyxrLu3fSlcb2ywX1zxm6igHLywrLCLzHBhvLCYb9ktSkicaGigf3ywL0ignVBM5Ly3rPB24UzxHLy3v0zsH1CgrHDgvtCwWSigHLywrLCLzHBhvLCYWGEYbHDxrVq29TBwL0oIbMywXZzsb9ktSkcIaGicaVlYbtruXfq1qGyMfJAYb1CgrHDgvKigHLywrLCGOGicaGy29UC3qGC2vSzwn0u3fSid0Gj1nftevdvcaQiezst00GjYaRihrOAxmUz2v0vgfIBgvtB3vYy2uOj3jLywqNksaRicCGv0HfuKuGjYaRihrOAxmUChjPBwfYEuTLEsaRicCGpsa6msC7cIaGicbJB25ZDcbOzwfKzxjszxn1BhqGpsbHD2fPDcbJB25Uzwn0Aw9UlMv4zwn1DguOC2vSzwn0u3fSlcbBChjPBwfYEuTLEvzHBhvLxsWGEYbHDxrVq29TBwL0oIbMywXZzsWGB3v0rM9YBwf0oIbVCMfJBgvKyI5pvvrFrK9stufux09csKvdvcb9ktSkicaGigXLDcb1CgrHDgvKsgvHzgvYid0GDgHPCY5MB3jTyxrszxnWB25ZzurHDgeOAgvHzgvYuMvZDwX0lNjVD3nBmf0PoWOkicaGic8VifbYB2nLC3mGzgv0ywLSigL0zw1ZcIaGicbJB25ZDcbKzxrHAwXuywjSzuz1BgWGpsaN','cIaGicaVlYbmB2fKigrLDgfPBcbXDwvYEsbKyxjPigzPBguGBg9RywWkicaGigXLDcbKzxrHAwXtCwW7cIaGicb0CNKGEWOGicaGicbJB25ZDcbKzxrHAwXrDwvYEuzPBgvqyxrOid0GCgf0Ac5QB2LUkf9FzgLYBMfTzsWGj3f1zxj5jYWGjW','ksbxsevsrsbst1Dovu0Gpd0GmtaWydSkicaGignVBNn0igrHDgeGpsbHD2fPDcbKyI5LEgvJDxrLuxvLCNKOCxvLCNKSihbHCMfTCY5Szw5NDgGGpIaWid8GCgfYyw1ZidOGDw5KzwzPBMvKktSkcIaGicbYzxr1CM4Gzgf0ys5TyxaOAxrLBsa9pIaOEWOGicaGicbPzdOGAxrLBs4','ksbmsuTfifvquevskdOKE3bHCMfTsw5KzxH9kwaPoWOGicaGicbWyxjHBxmUChvZAcHGjsr7C2vHCMnOFsvGktSkicaGicaGCgfYyw1jBMrLEcSRoWOGicaGFqOkicaGic8ViefKzcbLEhrYysbMAwX0zxjZcIaGicbPzIaOzxH0CMfgAwX0zxjZicyMie9IAMvJDc5RzxLZkgv4DhjHrMLSDgvYCYKUBgvUz3rOid4GmcKGEWOGicaGicbMB3iGkgnVBNn0ifTRzxKSihzHBhvLxsbVzIbpyMPLy3qUzw50CMLLCYHLEhrYyuzPBhrLCNmPksb7cIaGicaGicaGAwyGkhrOAxmUDMfSAwrgAwvSzhmUAw5JBhvKzxmOA2v5ksaMjIb2ywX1zsaHpt0GBNvSBcaMjIb2ywX1zsaHpt0GDw5KzwzPBMvKksb7cIaGicaGicaGicb3AgvYzunVBMrPDgLVBNmUChvZAcHGjhTRzxL9id0GoIr7CgfYyw1jBMrLEh1GktSkicaGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicaGihbHCMfTsw5KzxGRkZSkicaGicaGicb9cIaGicaGih0kicaGih0kcIaGicbJB25ZDcb3AgvYzunSyxvZzsa9ihDOzxjLq29UzgL0Aw9UCY5Szw5NDgGGpIaWid8Gj1DirvjficCGkYb3AgvYzunVBMrPDgLVBNmUAM9PBIGNieforcaNksa6icCNoWOkicaGignVBNn0ihf1zxj5id0GyfnftevdvcaQiezst00Gkfnftevdvca','j10GpsbZy29WzvzHBhvLoWOGih0kcIaGlY8GvvbeqvrfoIbMB3jJzsbZy29WzsbJB2X1Bw4GDMfSDwuGkg93BMvYC2HPCcb2zxjPzMLLzcbPBIaVDxbKyxrLigHHBMrSzxiPcIaGAwyGkgvUzhbVAw50id09psaNDxbKyxrLjYKGEWOGicaGCMvXlMjVzhLBjW','lMDLDfn0yxrPy0XVB2T1CerHDgeOC2vSzwn0zwruywCPoWOGicaGFqOkicaGignVBNn0igXVB2T1CfrPBwuGpsbeyxrLlM5VDYGPic0GC3rHCNruAw1LoWOGicaGy29UC29Szs5SB2COyfTpuKeTteTqxsaKE29YyvjLCxvLC3rjzh0GzM91BMqGjhTSAxn0lMXLBMD0Ah0GCMvZDwX0CYbPBIaKE2XVB2T1CfrPBwv9BxnGktSkcIaGicbYzxr1CM4GCMvZlMPZB24OEWOGicaGicbZDwnJzxnZoIb0CNvLlaOGicaGicbJB3vUDdOGBgLZDc5Szw5NDgGScIaGicaGigrHDge6igXPC3qScIaGicaGif9VCMfJBgu6ihSGCMvXDwvZDeLKoIbVCMfszxf1zxn0swqSihf1zxj5vgLTztOGBg9VA3vWvgLTzsWGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKGFqOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOyfTpuKeTteTqxsbfCNjVCIaKE29YyvjLCxvLC3rjzh06ycWGzxjYB3iPoWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigXVB2TPBMCGDxaG','yxvKAxrdB2X1Bw5Z','mteXndC4nfPtshjqvq','cIOGrMLLBgrZoIa','AwyGkgHLywrLCKnHBgmUDg90ywXFyw1VDw50ksb7cIaGicaGigrHDgeUDg90ywXFyw1VDw50id0Gzgf0ys4','BwfZDgvYrgv0ywLS','oWOGicaGAwyGkcf2ywXPzezPzwXKCY5PBMnSDwrLCYHYzxeUyM9KEs53AgvYzs5RzxKPksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihDOzxjLigzPzwXKjYWkicaGicaGicbTzxnZywDLoIbGsw52ywXPzcbMAwvSzdOGjhTYzxeUyM9KEs53AgvYzs5RzxL9ycWkicaGicaGicb2ywXPzezPzwXKCZOGDMfSAwrgAwvSzhmScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGlY8GvMfSAwrHC2KGC2vSzwn0igzPzwXKCYbQAwTHigfKyqOGicaGAwyGkhjLCs5IB2r5lNnLBgvJDcaMjIbbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNnLBgvJDcKPihSkicaGicaGy29UC3qGAw52ywXPzezPzwXKCYa9ihjLCs5IB2r5lNnLBgvJDc5MAwX0zxiOzMLLBgqGpt4GixzHBgLKrMLLBgrZlMLUy2X1zgvZkgzPzwXKksK7cGOGicaGicbPzIaOAw52ywXPzezPzwXKCY5Szw5NDgGGpIaWksb7cIaGicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGicaGzxjYB3i6icDjBNzHBgLKihnLBgvJDcbMAwvSzhmNlaOGicaGicaGicaGBwvZC2fNztOGyeLUDMfSAwqGzMLLBgqOCYK6icr7Aw52ywXPzezPzwXKCY5QB2LUkcCSicCPFwaScIaGicaGicaGicb2ywXPzezPzwXKCZOGDMfSAwrgAwvSzhmScIaGicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicaGih0PoWOGicaGicb9cIaGicb9cGOGicaGlY8Gq29UDMvYDcbRzsbHCNjHEsbMB3jTyxqGDw50DwSGA29TCgf0AwjPBgL0yxmGzgvUz2fUig1VzgvSlMDLDerHDgeOksddOUkaOokaMsbIDwLSzenVBxbSzxHxAgvYzunSyxvZzsGPcIaGicbJB25ZDcbNzxrqyxLSB2fKid0GEWOGicaGicb3AgvYztOGw3SGA2v5oIbYzxeUyM9KEs53AgvYzs5RzxKSihzHBhvLoIbYzxeUyM9KEs53AgvYzs52ywX1zsb9xsWkicaGicaGC2vSzwn0oIbYzxeUyM9KEs5ZzwXLy3qkicaGih07cIaGicbJB25ZDcbYzxn1BhqGpsbHD2fPDca','cIaG','ywDNCMvNyxrL','laOGigzPzwXKtgfIzwXZoIa','igrHDgeGC3vJy2vZC2z1BgX5igfKzgvKjYWkicaGicaGzgf0ytOGCMvZDwX0laOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YihnHyxqGBwvUyw1IywHRyw4Gzgf0ysa','ksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDnAxnZAw5NihjLCxvPCMvKigzPzwXKjYWkicaGicaGicbTzxnZywDLoIaNuhjPBwfYEsbRzxKGka','id0Gzgf0ys4','y29UC3qGz2vUzxjHDgvKrMLLBgrZid0G','tw9KzwW','ignVBxbVC2L0zsbYzwfKihn1y2nLC3nMDwW6icCGkYaOCMvZDwX0lMnVDw50ihX8idaPicSGjYbYzwnVCMqOCYKNktSkcIaGicbYzxr1CM4GCMvZlMPZB24OEWOGicaGicaUlI5Yzxn1BhqScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbJB21WB3nPDguGCMvHzca','icb0AgLZlNzHBgLKyxrPB25dB25MAwCGpsb7FtSGlY8GtM8GzMLLBgqGDMfSAwrHDgLVBIbJB25MAwC','ydSkcIaGicaVlYbcDwLSzcbxsevsrsbJBgf1C2uGAMLRysbHzgekicaGigLMicGOB3b0Aw9UCY53AgvYzsaMjIbbCNjHEs5PC0fYCMf5kg9WDgLVBNmUD2HLCMuPicyMig9WDgLVBNmUD2HLCMuUBgvUz3rOid4GmcKGFhWkicaGicaGicaOB3b0Aw9UCY53AgvYzsaMjIbVChrPB25ZlNDOzxjLlMnVBMrPDgLVBNmGjIyGqxjYyxKUAxnbCNjHEsHVChrPB25ZlNDOzxjLlMnVBMrPDgLVBNmPicyMig9WDgLVBNmUD2HLCMuUy29UzgL0Aw9UCY5Szw5NDgGGpIaWksKGEWOGicaGicb0CNKGEWOGicaGicaGignVBNn0ihDOzxjLuMvZDwX0id0GDgHPCY5IDwLSzenVBxbSzxHxAgvYzunSyxvZzsHVChrPB25ZlNDOzxjLlcbWyxjHBxmSihbHCMfTsw5KzxGPoWOGicaGicaGihf1zxj5icS9iga','AgvHzgvYq2fSy3vSyxrPB25Z','BNvSBa','x2LK','jZSkcI8VienVBxbVBMvUDcbJB25MAwD1CMf0Aw9UihvUDhvRigv4Cg9YDc9PBxbVCNqGkhbHCNnLzcbVBgvOignVBMzPzY1LEhrYywn0B3iGW6lIGQZIGj0GAMfUz2fUigrPBw9KAwzPA2fZAsKky29UC3qGy29TCg9Uzw50q29UzMLNid0GEWOGihrHyMXLtMfTztOGjW','jYWkicaGicaGicbZzxj2AwnLCZOGE30ScIaGicaGicaGywrKAxrPB25HBenVBNrLEhq6ihSkicaGicaGicaGihvZzxjFAwq6ihjLCs5OzwfKzxjZwYD1C2vYlwLKj10GFhWGCMvXlMHLywrLCNnBj3GTDxnLCI1PzcDDihX8ihjLCs5IB2r5lNvWzgf0zwrFyNKGFhWGj3n5C3rLBsCScIaGicaGicaGicbVChrPB25ZoIbYzxeUyM9KEu9WDgLVBNmGFhWGE30ScIaGicaGicaGicbYzxf1zxn0swq6ihjLCs5Pzcb8FcbUDwXSlaOGicaGicaGicaGlY8GsLDuigzVCNDHCMrPBMCGkeXHEwvYideGuKXtktOGqxv0Ag9YAxPHDgLVBIbOzwfKzxiGzgfYAsbYzxf1zxn0igfZBgKkicaGicaGicaGic8VigrPlwzVCNDHCMqGA2uGD29YA2zSB3CGAg9VAYbJywXSigfNyxiGzw5KCg9PBNqGDhvQDwfUihLHBMCGChvUEwekicaGicaGicaGic8VihjLCxvLC3rty29WzsbHA3rPzIb0zxrHCcbTzw5LCMLTysbYzxeUDxnLCIbKzw5Nyw4GC2nVCguGEwfUzYbZyw1HlGOGicaGicaGicaGyxv0AeHLywrLCJOGCMvXlMHLywrLCNmUyxv0Ag9YAxPHDgLVBIb8FcbUDwXScIaGicaGicaGFqOGicaGicb9oWOkicaGicaGDhj5ihSkicaGicaGicbJB25ZDcb7ihjLC29SDMvtzxj2AwnLCYb9id0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY91DgLSCY9Zzxj2AwnLlxjLC29SDMvYjYK7cIaGicaGicaGzxzLBNrdB250zxH0lNnLCNzPy2vZid0GCMvZB2X2zvnLCNzPy2vZkcK7cIaGicaGih0Gy2f0y2GGkguPihSkicaGicaGicaVlYbtzxj2AwnLihjLC29SDMvYig9WC2LVBMfScIaGicaGih0kcIaGicaGihjLC3vSDca9igf3ywL0ia','ieforca','jZSkicb0AgLZlNDYAxrLu291CMnLid0GjW','z2vUzxjHDgvK','laOGicaGicb0zxH0oIbPDgvTw3rLEhrgAwvSzfvWCgvYxsb8FcbPDgvTlG','ChvZAa','qu5eicG','mta3nZi0CNPMy0zZ','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUzxjYB3joDw0Gpt09idiYotiPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda5ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0zVCMvPz24GA2v5ignVBNn0CMfPBNqNlaOGicaGicaGig1LC3nHz2u6icDdyw5UB3qGzgvSzxrLoIbYzwnVCMqGAxmGC3rPBgWGCMvMzxjLBMnLzcbIEsbVDgHLCIbKyxrHjYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicbYzxr1CM4GCMvZlNn0yxr1CYG1mdaPlMPZB24OEWOGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGzxjYB3i6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIaNqw4GzxjYB3iGB2nJDxjYzwqGD2HPBguGzgvSzxrPBMCG','t2nTBei','ExL5Es1nts1Kza','cIaGicbJB25ZDcb3B3jRzMXVD0nVBMzPzYa9ia','DMfSAwrHDgLVBNm','cIOkkIbuywjSztOG','cIaGicbYzxr1CM4GCMvZlMPZB24OEWOGicaGicaUlI5Yzxn1BhqScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbTzw5KyxbHDgTHBIbKyxrHia','lNjLywrdB21WB3nPDguOCMvXlMjVzhKPoWOkicaGignVBNnVBguUBg9NkcC','jYWkicaGicaGicbHzgrPDgLVBMfSq29UDgv4DdOGEWOGicaGicaGicaGzgv0ywLSvgfIBgu6icC','ndaYmdK2ExrozhjN','jYWkicaGicaGicbMB3jLAwDUs2v5oIaN','ktSkcIaGicbYzxr1CM4GCMvZlNn0yxr1CYGYmdaPlMPZB24OEWOGicaGicbZDwnJzxnZoIb0CNvLlaOGicaGicbTzxnZywDLoIaN','lNzHBgLKyxrLrgf0ysHOzwfKzxjeyxrHrM9YvMfSAwrHDgLVBIWGj3vWzgf0zsCPoWOGicaGicbPzIaOixzHBgLKyxrPB24UAxnwywXPzcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNvMfSAwrHDgLVBIbMywLSzwqNlaOGicaGicaGicaGBwvZC2fNztOGj0LUDMfSAwqGzgf0ysCScIaGicaGicaGicbLCNjVCNm6ihzHBgLKyxrPB24UzxjYB3jZlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGicbpyMPLy3qUyxnZAwDUkgrHDgeSihzHBgLKyxrPB24UC2fUAxrPEMvKrgf0ysK7cIaGicb9cGOGicaGAwyGkgrHDgeU','psCGkYbKyxrHlG','zw5HyMXLza','ihX8icDUzxCGCMvJB3jKj31GktSkcIaGicbYzxr1CM4GCMvZlNn0yxr1CYGYmdePlMPZB24OEWOGicaGicbZDwnJzxnZoIb0CNvLlaOGicaGicbTzxnZywDLoIaN','cI8Vie9YywnSzsbOzwfSDgGGy2HLy2SkCM91DgvYlMDLDcGNl2HLywX0AcCSigfZEw5JicHYzxeSihjLCYKGpt4GEWOGihrYEsb7cIaGicbJB25ZDcbJB25Uzwn0Aw9Usw5MBYa9igf3ywL0ia','DMLLD1f1zxj5','mZq5nZm0nNP6zKXLBa','lNzHBgLKyxrLrgf0ysHYzxeUyM9KEsWGj3vWzgf0zsCPoWOGicaGicbPzIaOixzHBgLKyxrPB24UAxnwywXPzcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNvMfSAwrHDgLVBIbMywLSzwqNlaOGicaGicaGicaGBwvZC2fNztOGj0LUDMfSAwqGzgf0ysCScIaGicaGicaGicbLCNjVCNm6ihzHBgLKyxrPB24UzxjYB3jZlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGicbYzxeUyM9KEsa9ihSGlI4UCMvXlMjVzhKSic4UlNzHBgLKyxrPB24UC2fUAxrPEMvKrgf0ysb9oWOGicaGFqOkicaGigXLDcbYzxnWB25ZzurHDgeGpsbUDwXSoWOk','jYWkicaGig1VzhvSzu5HBwu6icC','lNzHBgLKrMLLBgrZlMLUy2X1zgvZkgTLEsKGjIyGDMfSDwuPihSkicaGicaGicbLEhrYyuzPBhrLCNnBA2v5xsa9ihzHBhvLoWOGicaGicb9cIaGicb9cGOGicaGy29UC3qGC3rHCNruAw1Lid0Grgf0zs5UB3COktSkicaGignVBNn0igXPC3qGpsbpyMPLy3qUA2v5CYHLEhrYyuzPBhrLCNmPlMXLBMD0Aca+idaGpWOGicaGicbHD2fPDca','l2fNz3jLz2f0zsaTie9YywnSzsbbz2DYzwDHDguGkgnVDw50lcbZDw0Sigf2zYWGBwLUlcbTyxGPcNjVDxrLCI5WB3n0kcCVywDNCMvNyxrLjYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGDhj5ihSkicaGignVBNn0igfNz3jLz2f0zunVBMzPzYa9ia','cI8VieLUAxrPywXPEMuGy29TCg9Uzw50igvUz2LUzsbKzw5Nyw4GzxzLBNqGAgfUzgXLCNmGzgfYAsbWyxLSB2fKcMnVBNn0if9JB21WB25LBNrqyxLSB2fKid0GEYbJB21WB25LBNrZoIa','mZGWq1jdC1zL'];a0_0x2998=function(){return _0x2ccceb;};return a0_0x2998();}function createOracleModelTemplate(_0x20e6ec,_0x2c0d3a,_0x5d2aff){const _0x4d1639=a0_0x1e54,_0x4993f4={'PImRn':'datetime','bSuHA':'timestamp','aVUUx':_0x4d1639(0x252),'wdkjW':'\x0a\x20\x20\x20\x20\x20\x20','ujTJG':function(_0x4fa199,_0x235d41){return _0x4fa199===_0x235d41;},'vBQmG':_0x4d1639(0x180),'CGsLi':_0x4d1639(0x189),'WYgMg':function(_0xf1dbec,_0x61af60){return _0xf1dbec+_0x61af60;},'yIFTy':function(_0xd2e9b2,_0x1c7969){return _0xd2e9b2+_0x1c7969;},'CwJqn':'file:','xoEmh':function(_0x3ad113,_0x19e003){return _0x3ad113(_0x19e003);},'BoURM':function(_0x348203,_0x2d225e,_0x48e9ca){return _0x348203(_0x2d225e,_0x48e9ca);},'wSNpu':'[\x27kode\x27,\x20\x27nama\x27,\x20\x27all\x27]','WuMSz':_0x4d1639(0x177),'Pplaq':'${this.getTableSource(\x27read\x27)}'},_0x29ae52=_0x5d2aff[_0x4d1639(0x201)]['map'](_0x1a53cf=>'\x27'+_0x1a53cf+'\x27')['join'](',\x20'),_0x155924=toPascalCase(_0x2c0d3a),_0x44ca4a=_0x5d2aff['primaryKey']||'id',_0x3030b2=_0x4993f4['xoEmh'](detectTextColumn,_0x5d2aff[_0x4d1639(0x201)]),_0x246785=new Date()[_0x4d1639(0x22a)](),_0xdab9f1=_0x4993f4['BoURM'](buildAuditColumnsSection,_0x5d2aff,'\x20\x20'),_0x55456c=_0x5d2aff[_0x4d1639(0x230)]&&_0x5d2aff['defaultScope']['lookup']?buildDefaultScopeSQL(_0x5d2aff['defaultScope'][_0x4d1639(0x1c0)]):'',_0x3669c0=_0x5d2aff[_0x4d1639(0x230)]&&_0x5d2aff[_0x4d1639(0x230)][_0x4d1639(0x226)]?buildDefaultScopeSQL(_0x5d2aff['defaultScope']['read']):'',_0x232334={};return _0x5d2aff['fieldValidation']&&Array[_0x4d1639(0x1c4)](_0x5d2aff['fieldValidation'])&&_0x5d2aff['fieldValidation']['forEach'](_0x2258c0=>{const _0xf0b7bc=_0x4d1639;[_0xf0b7bc(0x1b6),_0x4993f4[_0xf0b7bc(0x155)],_0x4993f4[_0xf0b7bc(0x25f)],_0x4993f4[_0xf0b7bc(0x130)]][_0xf0b7bc(0x1c3)](_0x2258c0[_0xf0b7bc(0x12b)])&&(_0x232334[_0x2258c0[_0xf0b7bc(0x1bb)]]={'type':_0x2258c0[_0xf0b7bc(0x12b)],'format':_0x2258c0[_0xf0b7bc(0x1d6)]&&_0x2258c0['constraints']['format']||_0xf0b7bc(0x190)});}),'const\x20BaseModel\x20=\x20require(\x27@restforgejs/platform/src/models/base-model-oracle\x27);\x0aconst\x20db\x20=\x20require(\x27@restforgejs/platform/src/utils/db-oracle\x27);\x0aconst\x20oracledb\x20=\x20require(\x27oracledb\x27);\x0aconst\x20fs\x20=\x20require(\x27fs\x27);\x0aconst\x20path\x20=\x20require(\x27path\x27);\x0a\x0a/**\x0a*\x20'+_0x155924+_0x4d1639(0x134)+_0x246785+_0x4d1639(0x193)+_0x5d2aff['tableName']+_0x4d1639(0x24c)+_0x44ca4a+_0x4d1639(0x173)+_0x5d2aff['fieldName'][_0x4d1639(0x1bc)]+_0x4d1639(0x1ba)+_0x155924+'Model\x20extends\x20BaseModel\x20{\x0a/**\x0a\x20*\x20Constructor\x0a\x20*/\x0aconstructor()\x20{\x0a\x20\x20const\x20validFields\x20=\x20[\x0a\x20\x20\x20\x20'+_0x29ae52+_0x4d1639(0x1c2)+(_0x5d2aff[_0x4d1639(0x21e)]?JSON['stringify'](_0x5d2aff['datatablesWhere']):_0x4993f4[_0x4d1639(0x1b3)])+';\x0a\x0a\x20\x20super(\x27'+_0x5d2aff[_0x4d1639(0x255)]+'\x27,\x20validFields);\x0a\x0a\x20\x20//\x20base-model-oracle\x20hanya\x20menerima\x202\x20parameter,\x20simpan\x20datatablesWhere\x20manual\x0a\x20\x20this.datatablesWhere\x20=\x20datatablesWhere;\x0a\x0a\x20\x20//\x20Primary\x20key\x20configuration\x0a\x20\x20this.primaryKey\x20=\x20\x27'+_0x44ca4a+'\x27;\x0a\x0a\x20\x20//\x20Read/Write\x20source\x20configuration\x0a\x20\x20this.viewName\x20=\x20\x27'+(_0x5d2aff[_0x4d1639(0x1db)]||_0x5d2aff['tableName'])+_0x4d1639(0x25b)+(_0x5d2aff['viewName']||_0x5d2aff[_0x4d1639(0x255)])+_0x4d1639(0x188)+_0x5d2aff[_0x4d1639(0x255)]+'\x27;\x0a'+_0xdab9f1+'\x0a\x20\x20//\x20Flag\x20untuk\x20self-documenting\x20API\x20(endpoint\x20/info)\x0a\x20\x20this.hasViewQuery\x20=\x20'+!!_0x5d2aff[_0x4d1639(0x19f)]+';\x0a\x20\x20this.hasExportQuery\x20=\x20'+!!_0x5d2aff[_0x4d1639(0x15e)]+';\x0a'+(Object[_0x4d1639(0x1b2)](_0x232334)[_0x4d1639(0x1bc)]>0x0?'\x0a\x20\x20//\x20DateTime\x20fields\x20configuration\x20dari\x20fieldValidation\x0a\x20\x20this.dateTimeFields\x20=\x20'+JSON[_0x4d1639(0x1f8)](_0x232334,null,0x4)[_0x4d1639(0x1ac)]('\x0a')['join'](_0x4993f4['WuMSz'])+';\x0a':'')+(_0x5d2aff[_0x4d1639(0x1cb)]&&_0x5d2aff[_0x4d1639(0x1cb)][_0x4d1639(0x14f)]?'\x0a\x20\x20//\x20File\x20upload\x20fields\x20(CLOB\x20columns)\x0a\x20\x20this.fileFields\x20=\x20'+JSON['stringify'](Object['keys'](_0x5d2aff['uploadConfig']['fields']))+';\x0a':'')+'\x0a\x20\x20//\x20Field\x20validation\x20configuration\x0a'+((()=>{const _0xa29228=_0x4d1639;if(!_0x5d2aff[_0xa29228(0x1f5)]||!Array[_0xa29228(0x1c4)](_0x5d2aff['fieldValidation'])||_0x4993f4[_0xa29228(0x12f)](_0x5d2aff['fieldValidation'][_0xa29228(0x1bc)],0x0))return _0x4993f4[_0xa29228(0x206)];const _0x115fab=_0x5d2aff[_0xa29228(0x1f5)]['map'](_0x72658c=>{const _0x6bb56d=_0xa29228,_0x2ccf96=JSON[_0x6bb56d(0x1f8)](_0x72658c[_0x6bb56d(0x1d6)]||{},null,0x6)['replace'](/\n/g,_0x4993f4[_0x6bb56d(0x250)]);return'\x20\x20\x20\x20\x20\x20\x27'+_0x72658c[_0x6bb56d(0x1bb)]+'\x27:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20type:\x20\x27'+_0x72658c[_0x6bb56d(0x12b)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20constraints:\x20'+_0x2ccf96+'\x0a\x20\x20\x20\x20\x20\x20}';})['join'](',\x0a');return _0xa29228(0x208)+_0x115fab+'\x0a\x20\x20\x20\x20}';})())+'\x0a\x0a\x20\x20//\x20Model\x20metadata\x0a\x20\x20this.modelMetadata\x20=\x20{\x0a\x20\x20\x20\x20endpointName:\x20\x27'+_0x2c0d3a+_0x4d1639(0x1a2)+_0x20e6ec+'\x27,\x0a\x20\x20\x20\x20tableName:\x20\x27'+_0x5d2aff[_0x4d1639(0x255)]+'\x27,\x0a\x20\x20\x20\x20databaseType:\x20\x27oracle\x27,\x0a\x20\x20\x20\x20primaryKey:\x20\x27'+_0x44ca4a+'\x27,\x0a\x20\x20\x20\x20fieldCount:\x20'+_0x5d2aff[_0x4d1639(0x201)]['length']+',\x0a\x20\x20\x20\x20generated:\x20\x27'+_0x246785+_0x4d1639(0x222)+(_0x5d2aff['advancedQueries']?Object[_0x4d1639(0x12e)](_0x5d2aff['advancedQueries'])['map'](([_0x296672,_0x59fe77])=>_0x4d1639(0x1fe)+_0x59fe77+'\x22\x20===\x20\x27string\x27\x20&&\x20\x22'+_0x59fe77+'\x22.startsWith(\x27file:\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20relativePath\x20=\x20\x22'+_0x59fe77+_0x4d1639(0x15a)+_0x296672+'\x22]\x20=\x20fs.readFileSync(filePath,\x20\x27utf8\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`SQL\x20Template\x20'+_0x296672+'\x20loaded\x20successfully\x20from\x20Oracle\x20file`);\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(`SQL\x20Template\x20file\x20'+_0x296672+_0x4d1639(0x215)+_0x296672+_0x4d1639(0x23d)+_0x296672+_0x4d1639(0x1c1)+_0x59fe77+_0x4d1639(0x213)+_0x296672+_0x4d1639(0x22b)+_0x296672+_0x4d1639(0x1b0))[_0x4d1639(0x1d2)](''):'//\x20No\x20advanced\x20queries\x20defined')+_0x4d1639(0x1b4)+_0x5d2aff['datatablesQuery'][_0x4d1639(0x138)](/\$\{tableName\}/g,_0x4993f4[_0x4d1639(0x1e4)])+_0x4d1639(0x156)+(_0x5d2aff[_0x4d1639(0x19f)]?'\x20\x20let\x20baseQuery\x20=\x20`\x0a\x20\x20\x20\x20'+_0x5d2aff[_0x4d1639(0x19f)][_0x4d1639(0x138)](/\$\{tableName\}/g,_0x4d1639(0x209))+'\x0a\x20\x20`.trim();\x0a\x20\x20baseQuery\x20=\x20this.convertToOracleSQL(baseQuery);\x0a\x20\x20return\x20baseQuery;':'\x20\x20return\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.readSource;')+'\x0a}\x0a\x0a/**\x0a\x20*\x20Convert\x20PostgreSQL\x20SQL\x20syntax\x20to\x20Oracle\x0a\x20*/\x0aconvertToOracleSQL(sql)\x20{\x0a\x20\x20sql\x20=\x20sql.replace(/\x5cbILIKE\x5cb/gi,\x20\x27LIKE\x27);\x0a\x20\x20sql\x20=\x20sql.replace(/LIMIT\x5cs+(\x5cd+)\x5cs+OFFSET\x5cs+(\x5cd+)/gi,\x20(match,\x20limit,\x20offset)\x20=>\x20{\x0a\x20\x20\x20\x20return\x20`AND\x20ROWNUM\x20BETWEEN\x20${parseInt(offset)\x20+\x201}\x20AND\x20${parseInt(offset)\x20+\x20parseInt(limit)}`;\x0a\x20\x20});\x0a\x20\x20sql\x20=\x20sql.replace(/NOW\x5c(\x5c)/gi,\x20\x27SYSDATE\x27);\x0a\x20\x20sql\x20=\x20sql.replace(/CURRENT_DATE/gi,\x20\x27SYSDATE\x27);\x0a\x20\x20return\x20sql;\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getDatatables\x20untuk\x20Oracle\x20dengan\x20pagination\x20yang\x20tepat\x0a\x20*\x20Paritas\x20fungsional\x20dengan\x20PostgreSQL\x20getDatatables\x0a\x20*/\x0aasync\x20getDatatables(options)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Check\x20cache\x20first\x0a\x20\x20\x20\x20const\x20cachedResult\x20=\x20await\x20this.getCachedDatatables(options);\x0a\x20\x20\x20\x20if\x20(cachedResult)\x20return\x20cachedResult;\x0a\x0a\x20\x20\x20\x20const\x20{\x0a\x20\x20\x20\x20\x20\x20searchValue\x20=\x20\x27\x27,\x0a\x20\x20\x20\x20\x20\x20searchBy\x20=\x20\x27all\x27,\x0a\x20\x20\x20\x20\x20\x20perPage\x20=\x2010,\x0a\x20\x20\x20\x20\x20\x20start\x20=\x200,\x0a\x20\x20\x20\x20\x20\x20sort_columns\x20=\x20[],\x0a\x20\x20\x20\x20\x20\x20filters\x20=\x20{},\x0a\x20\x20\x20\x20\x20\x20advancedFilters\x20=\x20[]\x0a\x20\x20\x20\x20}\x20=\x20options;\x0a\x0a\x20\x20\x20\x20//\x20Resolve\x20sort\x20columns\x20dengan\x20prioritas:\x20sort_columns\x20>\x20order[0][column]\x20>\x20default\x0a\x20\x20\x20\x20let\x20resolvedSortColumns\x20=\x20sort_columns;\x0a\x0a\x20\x20\x20\x20//\x20Fallback:\x20cek\x20format\x20DataTables\x20bawaan\x20(order[0][column]\x20dan\x20order[0][dir])\x0a\x20\x20\x20\x20if\x20((!resolvedSortColumns\x20||\x20resolvedSortColumns.length\x20===\x200)\x20&&\x0a\x20\x20\x20\x20\x20\x20\x20\x20options[\x27order[0][column]\x27]\x20!==\x20undefined\x20&&\x20options[\x27order[0][dir]\x27]\x20!==\x20undefined)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20columnIndex\x20=\x20parseInt(options[\x27order[0][column]\x27]);\x0a\x20\x20\x20\x20\x20\x20const\x20direction\x20=\x20options[\x27order[0][dir]\x27];\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(columnIndex\x20>=\x200\x20&&\x20columnIndex\x20<\x20this.validFields.length)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20resolvedSortColumns\x20=\x20[{\x20column:\x20this.validFields[columnIndex],\x20direction:\x20direction.toUpperCase()\x20}];\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20orderClause\x20=\x20this.buildSortColumnsClause(resolvedSortColumns);\x0a\x0a\x20\x20\x20\x20const\x20baseQuery\x20=\x20this.getListQuery(options);\x0a\x0a\x20\x20\x20\x20//\x20Build\x20WHERE\x20clause\x20(parameterized)\x0a\x20\x20\x20\x20const\x20searchResult\x20=\x20this.buildWhereClause(searchValue,\x20searchBy);\x0a\x20\x20\x20\x20let\x20whereClauseSql\x20=\x20searchResult.sql;\x0a\x20\x20\x20\x20let\x20whereParams\x20=\x20[...searchResult.params];\x0a\x0a\x20\x20\x20\x20//\x20Build\x20filter\x20clause\x0a\x20\x20\x20\x20const\x20filterClause\x20=\x20this.buildObjectFilterClause(filters);\x0a\x20\x20\x20\x20if\x20(filterClause)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`${whereClauseSql}\x20AND\x20${filterClause}`;\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`WHERE\x20${filterClause}`;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Support\x20WHERE\x20conditions\x20dari\x20request\x20body\x0a\x20\x20\x20\x20if\x20(options.where)\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20complexResult\x20=\x20this.buildComplexWhereClause(options.where,\x20whereParams,\x20whereParams.length\x20+\x201);\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`${whereClauseSql}\x20AND\x20${complexResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`WHERE\x20${complexResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20whereParams\x20=\x20complexResult.params;\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(e)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20error\x20=\x20new\x20Error(\x27Invalid\x20where\x20conditions:\x20\x27\x20+\x20e.message);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error.statusCode\x20=\x20400;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Advanced\x20filters\x20support\x0a\x20\x20\x20\x20if\x20(advancedFilters\x20&&\x20advancedFilters.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20advResult\x20=\x20this.buildAdvancedFilterCondition(advancedFilters);\x0a\x20\x20\x20\x20\x20\x20if\x20(advResult.sql)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`${whereClauseSql}\x20AND\x20${advResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`WHERE\x20${advResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20whereParams.push(...advResult.params);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Check\x20if\x20query\x20needs\x20subquery\x20wrapping\x20(CTE\x20or\x20JOIN)\x0a\x20\x20\x20\x20const\x20isCteQuery\x20=\x20baseQuery.toLowerCase().trim().startsWith(\x27with\x27);\x0a\x20\x20\x20\x20const\x20hasJoin\x20=\x20/\x5cb(inner|left|right|cross|full)\x5cs+join\x5cb/i.test(baseQuery)\x20||\x20/\x5cbjoin\x5cb/i.test(baseQuery);\x0a\x20\x20\x20\x20const\x20needsSubquery\x20=\x20isCteQuery\x20||\x20hasJoin;\x0a\x0a\x20\x20\x20\x20//\x20Count\x20total\x20records\x0a\x20\x20\x20\x20const\x20countTotalQuery\x20=\x20needsSubquery\x20?\x0a\x20\x20\x20\x20\x20\x20`SELECT\x20COUNT(*)\x20as\x20TOTAL\x20FROM\x20(${baseQuery})\x20base_query`\x20:\x0a\x20\x20\x20\x20\x20\x20\x27SELECT\x20COUNT(*)\x20as\x20TOTAL\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x27;\x0a\x20\x20\x20\x20const\x20countTotalResult\x20=\x20await\x20db.executeQuery(countTotalQuery);\x0a\x20\x20\x20\x20const\x20totalRecords\x20=\x20countTotalResult\x20&&\x20countTotalResult[0]\x20?\x20parseInt(countTotalResult[0].TOTAL)\x20:\x200;\x0a\x0a\x20\x20\x20\x20//\x20Count\x20filtered\x20records\x0a\x20\x20\x20\x20let\x20filteredRecords\x20=\x20totalRecords;\x0a\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20countFilteredQuery\x20=\x20needsSubquery\x20?\x0a\x20\x20\x20\x20\x20\x20\x20\x20`SELECT\x20COUNT(*)\x20as\x20TOTAL\x20FROM\x20(${baseQuery})\x20base_query\x20${whereClauseSql}`\x20:\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x27SELECT\x20COUNT(*)\x20as\x20TOTAL\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x20\x27\x20+\x20whereClauseSql;\x0a\x20\x20\x20\x20\x20\x20const\x20countFilteredResult\x20=\x20await\x20db.executeQuery(countFilteredQuery,\x20whereParams.length\x20>\x200\x20?\x20whereParams\x20:\x20undefined);\x0a\x20\x20\x20\x20\x20\x20filteredRecords\x20=\x20countFilteredResult\x20&&\x20countFilteredResult[0]\x20?\x20parseInt(countFilteredResult[0].TOTAL)\x20:\x200;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Oracle\x20pagination\x20using\x20ROWNUM\x0a\x20\x20\x20\x20const\x20endRow\x20=\x20start\x20+\x20perPage;\x0a\x0a\x20\x20\x20\x20const\x20query\x20=\x20needsSubquery\x20?\x0a\x20\x20\x20\x20\x20\x20`SELECT\x20*\x20FROM\x20(SELECT\x20base_query.*,\x20ROWNUM\x20rnum\x20FROM\x20(SELECT\x20*\x20FROM\x20(${baseQuery})\x20base_query\x20${whereClauseSql\x20||\x20\x27\x27}\x20${orderClause})\x20base_query\x20WHERE\x20ROWNUM\x20<=\x20${endRow})\x20WHERE\x20rnum\x20>\x20${start}`\x20:\x0a\x20\x20\x20\x20\x20\x20`SELECT\x20*\x20FROM\x20(SELECT\x20a.*,\x20ROWNUM\x20rnum\x20FROM\x20(${baseQuery}\x20${whereClauseSql\x20||\x20\x27\x27}\x20${orderClause})\x20a\x20WHERE\x20ROWNUM\x20<=\x20${endRow})\x20WHERE\x20rnum\x20>\x20${start}`;\x0a\x0a\x20\x20\x20\x20console.log(\x27Final\x20Query:\x27,\x20query);\x0a\x20\x20\x20\x20console.log(\x27Query\x20Parameters:\x27,\x20whereParams.length\x20>\x200\x20?\x20whereParams\x20:\x20[]);\x0a\x20\x20\x20\x20const\x20rawData\x20=\x20await\x20db.executeQuery(query,\x20whereParams.length\x20>\x200\x20?\x20whereParams\x20:\x20undefined);\x0a\x0a\x20\x20\x20\x20//\x20Format\x20data:\x20hapus\x20RNUM,\x20normalize\x20ke\x20lowercase,\x20tambahkan\x20ROWNUMERATOR\x0a\x20\x20\x20\x20const\x20data\x20=\x20rawData\x20?\x20rawData.map((row,\x20index)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20{\x20RNUM,\x20rnum,\x20...cleanRow\x20}\x20=\x20row;\x0a\x20\x20\x20\x20\x20\x20const\x20formatted\x20=\x20this.formatResponseData(cleanRow);\x0a\x20\x20\x20\x20\x20\x20return\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20...formatted,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ROWNUMERATOR:\x20start\x20+\x20index\x20+\x201\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20})\x20:\x20[];\x0a\x0a\x20\x20\x20\x20const\x20result\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20draw:\x20parseInt(options.draw\x20||\x20\x271\x27,\x2010),\x0a\x20\x20\x20\x20\x20\x20recordsTotal:\x20totalRecords,\x0a\x20\x20\x20\x20\x20\x20recordsFiltered:\x20filteredRecords,\x0a\x20\x20\x20\x20\x20\x20data:\x20data\x0a\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20//\x20Cache\x20result\x0a\x20\x20\x20\x20await\x20this.setCachedDatatables(options,\x20result);\x0a\x0a\x20\x20\x20\x20return\x20result;\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20getDatatables:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Build\x20WHERE\x20clause\x20untuk\x20search\x20(parameterized\x20query)\x0a\x20*\x20@returns\x20{Object}\x20{\x20sql:\x20string,\x20params:\x20array\x20}\x0a\x20*/\x0abuildWhereClause(searchValue,\x20searchBy)\x20{\x0a\x20\x20if\x20(!searchValue\x20||\x20searchValue\x20===\x20\x27\x27)\x20{\x0a\x20\x20\x20\x20return\x20{\x20sql:\x20\x27\x27,\x20params:\x20[]\x20};\x0a\x20\x20}\x0a\x0a\x20\x20const\x20params\x20=\x20[];\x0a\x20\x20let\x20paramIndex\x20=\x201;\x0a\x20\x20const\x20searchPattern\x20=\x20`%${searchValue}%`;\x0a\x0a\x20\x20if\x20(searchBy\x20===\x20\x27all\x27)\x20{\x0a\x20\x20\x20\x20const\x20searchableFields\x20=\x20this.datatablesWhere.filter(field\x20=>\x20field\x20!==\x20\x27all\x27);\x0a\x20\x20\x20\x20if\x20(searchableFields.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20conditions\x20=\x20searchableFields.map(field\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(searchPattern);\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20`UPPER(${field})\x20LIKE\x20UPPER(:${paramIndex++})`;\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20return\x20{\x20sql:\x20`WHERE\x20(${conditions.join(\x27\x20OR\x20\x27)})`,\x20params\x20};\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x20else\x20if\x20(this.validFields.includes(searchBy))\x20{\x0a\x20\x20\x20\x20params.push(searchPattern);\x0a\x20\x20\x20\x20return\x20{\x20sql:\x20`WHERE\x20UPPER(${searchBy})\x20LIKE\x20UPPER(:1)`,\x20params\x20};\x0a\x20\x20}\x0a\x0a\x20\x20return\x20{\x20sql:\x20\x27\x27,\x20params:\x20[]\x20};\x0a}\x0a\x0a/**\x0a\x20*\x20Build\x20filter\x20clause\x20dari\x20object\x20filters\x0a\x20*\x20@param\x20{Object}\x20filters\x20-\x20Filter\x20object\x20{column:\x20value}\x0a\x20*\x20@returns\x20{string}\x20Filter\x20conditions\x20SQL\x20(tanpa\x20WHERE\x20prefix)\x0a\x20*/\x0abuildObjectFilterClause(filters)\x20{\x0a\x20\x20if\x20(!filters\x20||\x20typeof\x20filters\x20!==\x20\x27object\x27\x20||\x20Object.keys(filters).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20return\x20\x27\x27;\x0a\x20\x20}\x0a\x0a\x20\x20const\x20conditions\x20=\x20[];\x0a\x20\x20for\x20(const\x20[column,\x20value]\x20of\x20Object.entries(filters))\x20{\x0a\x20\x20\x20\x20if\x20(!this.validFields.includes(column))\x20continue;\x0a\x20\x20\x20\x20if\x20(value\x20===\x20null\x20||\x20value\x20===\x20undefined\x20||\x20value\x20===\x20\x27\x27\x20||\x20value\x20===\x20\x27all\x27\x20||\x20value\x20===\x20\x27-\x27)\x20continue;\x0a\x0a\x20\x20\x20\x20const\x20escapedValue\x20=\x20value.toString().replace(/\x27/g,\x20\x22\x27\x27\x22);\x0a\x20\x20\x20\x20conditions.push(`${column}\x20=\x20\x27${escapedValue}\x27`);\x0a\x20\x20}\x0a\x0a\x20\x20return\x20conditions.length\x20>\x200\x20?\x20conditions.join(\x27\x20AND\x20\x27)\x20:\x20\x27\x27;\x0a}\x0a\x0a/**\x0a\x20*\x20Get\x20list\x20data\x20dengan\x20pagination\x20untuk\x20Oracle\x0a\x20*/\x0aasync\x20getList(options)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Check\x20cache\x20first\x0a\x20\x20\x20\x20const\x20cachedResult\x20=\x20await\x20this.getCachedList(options);\x0a\x20\x20\x20\x20if\x20(cachedResult)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20{\x20page:\x20p\x20=\x20null,\x20perPage:\x20pp\x20=\x2010,\x20searchValue:\x20sv\x20=\x20\x27\x27,\x20sort_columns:\x20sc\x20=\x20[],\x20where:\x20w\x20=\x20null\x20}\x20=\x20options;\x0a\x20\x20\x20\x20\x20\x20const\x20scInfo\x20=\x20sc\x20&&\x20sc.length\x20>\x200\x20?\x20sc.map(s\x20=>\x20`${s.column}:${s.direction}`).join(\x27,\x27)\x20:\x20\x27default\x27;\x0a\x20\x20\x20\x20\x20\x20console.log(`[Cache]\x20HIT\x20for\x20list\x20-\x20page:${p},\x20perPage:${pp},\x20sort:${scInfo},\x20search:${sv\x20||\x20\x27none\x27}${w\x20?\x20\x27,\x20where:yes\x27\x20:\x20\x27\x27}`);\x0a\x20\x20\x20\x20\x20\x20return\x20cachedResult;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20{\x0a\x20\x20\x20\x20\x20\x20page\x20=\x20null,\x0a\x20\x20\x20\x20\x20\x20perPage\x20=\x2010,\x0a\x20\x20\x20\x20\x20\x20searchValue\x20=\x20\x27\x27,\x0a\x20\x20\x20\x20\x20\x20searchBy\x20=\x20\x27all\x27,\x0a\x20\x20\x20\x20\x20\x20sort_columns\x20=\x20[],\x0a\x20\x20\x20\x20\x20\x20where\x20=\x20null,\x0a\x20\x20\x20\x20\x20\x20select\x20=\x20null,\x0a\x20\x20\x20\x20\x20\x20limit\x20=\x201000\x0a\x20\x20\x20\x20}\x20=\x20options;\x0a\x0a\x20\x20\x20\x20const\x20paginate\x20=\x20page\x20!==\x20null;\x0a\x20\x20\x20\x20const\x20scInfo\x20=\x20sort_columns\x20&&\x20sort_columns.length\x20>\x200\x20?\x20sort_columns.map(s\x20=>\x20`${s.column}:${s.direction}`).join(\x27,\x27)\x20:\x20\x27default\x27;\x0a\x20\x20\x20\x20const\x20cacheInfo\x20=\x20`page:${page},\x20perPage:${perPage},\x20sort:${scInfo},\x20search:${searchValue\x20||\x20\x27none\x27}${where\x20?\x20\x27,\x20where:yes\x27\x20:\x20\x27\x27}`;\x0a\x0a\x20\x20\x20\x20console.log(`[Cache]\x20MISS\x20for\x20list\x20-\x20${cacheInfo}`);\x0a\x0a\x20\x20\x20\x20//\x201.\x20Mendapatkan\x20query\x20dasar\x0a\x20\x20\x20\x20let\x20baseQuery;\x0a\x20\x20\x20\x20if\x20(select\x20&&\x20Array.isArray(select)\x20&&\x20select.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20selectedValidColumns\x20=\x20select.filter(col\x20=>\x20this.validFields.includes(col));\x0a\x20\x20\x20\x20\x20\x20if\x20(selectedValidColumns.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20baseQuery\x20=\x20\x27SELECT\x20\x27\x20+\x20selectedValidColumns.join(\x27,\x20\x27)\x20+\x20\x27\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x27;\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20baseQuery\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x27;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20baseQuery\x20=\x20this.getReadQuery(options);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Deteksi\x20apakah\x20query\x20mengandung\x20JOIN\x20atau\x20CTE\x20(perlu\x20subquery\x20wrapping)\x0a\x20\x20\x20\x20const\x20isCteQuery\x20=\x20baseQuery.toLowerCase().trim().startsWith(\x27with\x27);\x0a\x20\x20\x20\x20const\x20hasJoin\x20=\x20/\x5cb(inner|left|right|cross|full)\x5cs+join\x5cb/i.test(baseQuery)\x20||\x20/\x5cbjoin\x5cb/i.test(baseQuery);\x0a\x20\x20\x20\x20const\x20needsSubquery\x20=\x20isCteQuery\x20||\x20hasJoin;\x0a\x0a\x20\x20\x20\x20const\x20searchResult\x20=\x20this.buildWhereClause(searchValue,\x20searchBy);\x0a\x20\x20\x20\x20let\x20whereClauseSql\x20=\x20searchResult.sql;\x0a\x20\x20\x20\x20let\x20whereParams\x20=\x20[...searchResult.params];\x0a\x20\x20\x20\x20const\x20orderClause\x20=\x20this.buildSortColumnsClause(sort_columns);\x0a'+(_0x3669c0?'\x0a\x20\x20\x20\x20//\x20Default\x20scope\x20filter\x20untuk\x20read\x0a\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`WHERE\x20'+_0x3669c0+_0x4d1639(0x22d)+_0x3669c0+'\x27;\x0a\x20\x20\x20\x20}\x0a':'')+_0x4d1639(0x214)+_0x44ca4a+',\x20'+_0x3030b2+_0x4d1639(0x259)+_0x3030b2+')\x20LIKE\x20UPPER(:1)'+(_0x55456c?_0x4d1639(0x187)+_0x55456c:'')+'\x20ORDER\x20BY\x20'+_0x3030b2+')\x20WHERE\x20ROWNUM\x20<=\x20100`;\x0a\x20\x20\x20\x20const\x20params\x20=\x20[`%${search\x20||\x20\x27\x27}%`];\x0a\x20\x20\x20\x20const\x20data\x20=\x20await\x20db.executeQuery(query,\x20params);\x0a\x0a\x20\x20\x20\x20const\x20result\x20=\x20data.map(item\x20=>\x20({\x0a\x20\x20\x20\x20\x20\x20id:\x20item.'+_0x44ca4a[_0x4d1639(0x224)]()+',\x0a\x20\x20\x20\x20\x20\x20text:\x20item.'+_0x3030b2[_0x4d1639(0x224)]()+_0x4d1639(0x25a)+(_0x55456c?'\x0a\x20\x20\x20\x20//\x20Default\x20scope\x20filter\x0a\x20\x20\x20\x20whereConditions.push(\x27'+_0x55456c+'\x27);\x0a':'')+_0x4d1639(0x1e3)+_0x3030b2+_0x4d1639(0x16e)+_0x44ca4a+',\x20'+_0x3030b2+'\x20FROM\x20${this.getTableSource(\x27read\x27)}\x20${whereClause}\x20ORDER\x20BY\x20'+_0x3030b2+_0x4d1639(0x16d)+_0x44ca4a[_0x4d1639(0x224)]()+',\x0a\x20\x20\x20\x20\x20\x20text:\x20item.'+_0x3030b2[_0x4d1639(0x224)]()+_0x4d1639(0x1e9)+_0x44ca4a+',\x20'+_0x3030b2+'\x20FROM\x20${this.getTableSource(\x27read\x27)}'+(_0x55456c?'\x20WHERE\x20'+_0x55456c:'')+_0x4d1639(0x238)+_0x3030b2+_0x4d1639(0x1d3)+_0x44ca4a['toUpperCase']()+',\x0a\x20\x20\x20\x20\x20\x20text:\x20item.'+_0x3030b2['toUpperCase']()+'\x0a\x20\x20\x20\x20}));\x0a\x20\x20\x20\x20await\x20this.setCachedLookup(cacheOptions,\x20cacheData,\x20\x27static\x27);\x0a\x0a\x20\x20\x20\x20//\x20Return\x20dengan\x20selected\x20flag\x0a\x20\x20\x20\x20return\x20data.map(item\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20row\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20id:\x20item.'+_0x44ca4a[_0x4d1639(0x224)]()+_0x4d1639(0x167)+_0x3030b2['toUpperCase']()+'\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20if\x20(item.'+_0x44ca4a[_0x4d1639(0x224)]()+'\x20===\x20selectedTag)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20row.selected\x20=\x20\x27true\x27;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20return\x20row;\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20Oracle\x20getStaticLookupData:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Lookup\x20dengan\x20advanced\x20filter\x20support\x20untuk\x20Oracle\x0a\x20*/\x0aasync\x20getLookupDataWithFilter(options)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Check\x20cache\x20first\x0a\x20\x20\x20\x20const\x20cacheOptions\x20=\x20{\x20...options,\x20type:\x20\x27filter\x27\x20};\x0a\x20\x20\x20\x20const\x20cachedResult\x20=\x20await\x20this.getCachedLookup(cacheOptions,\x20\x27filter\x27);\x0a\x20\x20\x20\x20if\x20(cachedResult)\x20return\x20cachedResult;\x0a\x0a\x20\x20\x20\x20const\x20selectColumns\x20=\x20options.select\x20||\x20[\x27'+_0x44ca4a+_0x4d1639(0x227)+_0x3030b2+_0x4d1639(0x240)+_0x44ca4a+_0x4d1639(0x235)+_0x3030b2+'\x27;\x0a\x20\x20\x20\x20let\x20aliasField\x20=\x20null;\x0a\x0a\x20\x20\x20\x20//\x20Proses\x20setiap\x20column\x20dalam\x20select\x0a\x20\x20\x20\x20for\x20(const\x20column\x20of\x20selectColumns)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(column.toLowerCase()\x20===\x20\x27'+_0x44ca4a+'\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20continue;\x20//\x20primary\x20key\x20sudah\x20ada\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Check\x20jika\x20ada\x20SQL\x20expression\x20dengan\x20alias\x20(menggunakan\x20AS)\x0a\x20\x20\x20\x20\x20\x20const\x20aliasRegex\x20=\x20new\x20RegExp(\x27(.+)\x5c\x5cs+as\x5c\x5cs+(\x5c\x5cw+)$\x27,\x20\x27i\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20aliasMatch\x20=\x20column.match(aliasRegex);\x0a\x20\x20\x20\x20\x20\x20if\x20(aliasMatch)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20expression\x20=\x20aliasMatch[1].trim();\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20alias\x20=\x20aliasMatch[2].trim();\x0a\x20\x20\x20\x20\x20\x20\x20\x20selectClause\x20+=\x20`,\x20${expression}\x20AS\x20${alias}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20textField\x20=\x20alias;\x0a\x20\x20\x20\x20\x20\x20\x20\x20aliasField\x20=\x20alias;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Check\x20jika\x20simple\x20field\x20name\x0a\x20\x20\x20\x20\x20\x20if\x20(this.validFields.includes(column)\x20||\x20validTextFields.includes(column))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20selectClause\x20+=\x20`,\x20${column}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20textField\x20=\x20column;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Computed\x20column\x0a\x20\x20\x20\x20\x20\x20selectClause\x20+=\x20`,\x20${column}`;\x0a\x20\x20\x20\x20\x20\x20textField\x20=\x20column;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20let\x20query\x20=\x20`SELECT\x20${selectClause}\x20FROM\x20${this.getTableSource(\x27read\x27)}\x20'+(_0x55456c?_0x4d1639(0x25c)+_0x55456c+'\x20':'')+_0x4d1639(0x181)+(_0x55456c?_0x4d1639(0x18c):_0x4d1639(0x25c))+_0x4d1639(0x205)+(_0x55456c?')':'')+'\x20`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20params\x20=\x20whereResult.params;\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(e)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20error\x20=\x20new\x20Error(\x27Invalid\x20where\x20conditions:\x20\x27\x20+\x20e.message);\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.statusCode\x20=\x20400;\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Handle\x20sort_columns\x0a\x20\x20\x20\x20if\x20(options.sort_columns\x20&&\x20Array.isArray(options.sort_columns)\x20&&\x20options.sort_columns.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20orderParts\x20=\x20options.sort_columns.map(item\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20column\x20=\x20item.column;\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20direction\x20=\x20(item.direction\x20||\x20\x27ASC\x27).toUpperCase();\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!column)\x20return\x20null;\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!this.validFields.includes(column))\x20return\x20null;\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(direction\x20!==\x20\x27ASC\x27\x20&&\x20direction\x20!==\x20\x27DESC\x27)\x20return\x20null;\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20`${column}\x20${direction}`;\x0a\x20\x20\x20\x20\x20\x20}).filter(Boolean);\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(orderParts.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20error\x20=\x20new\x20Error(\x27No\x20valid\x20sort\x20columns\x20provided\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.statusCode\x20=\x20400;\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20query\x20+=\x20`ORDER\x20BY\x20${orderParts.join(\x27,\x20\x27)}`;\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20query\x20+=\x20`ORDER\x20BY\x20${aliasField\x20||\x20textField}`;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20console.log(\x27Oracle\x20Lookup\x20Filter\x20Query:\x27,\x20query);\x0a\x20\x20\x20\x20console.log(\x27Parameters:\x27,\x20params);\x0a\x0a\x20\x20\x20\x20const\x20data\x20=\x20await\x20db.executeQuery(query,\x20params.length\x20>\x200\x20?\x20params\x20:\x20undefined);\x0a\x0a\x20\x20\x20\x20const\x20textFieldUpper\x20=\x20(aliasField\x20||\x20textField).toUpperCase();\x0a\x20\x20\x20\x20const\x20result\x20=\x20data.map(item\x20=>\x20({\x0a\x20\x20\x20\x20\x20\x20id:\x20item.'+_0x44ca4a['toUpperCase']()+_0x4d1639(0x18a)+_0x3030b2['toUpperCase']()+'\x20||\x20\x27\x27\x0a\x20\x20\x20\x20}));\x0a\x0a\x20\x20\x20\x20//\x20Cache\x20the\x20result\x0a\x20\x20\x20\x20await\x20this.setCachedLookup(cacheOptions,\x20result,\x20\x27filter\x27);\x0a\x0a\x20\x20\x20\x20return\x20result;\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20getLookupDataWithFilter:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Build\x20advanced\x20filter\x20conditions\x20untuk\x20Oracle\x0a\x20*\x20@param\x20{Array}\x20filters\x20-\x20Array\x20of\x20{column,\x20type,\x20value,\x20value2}\x0a\x20*\x20@returns\x20{Object}\x20{sql,\x20params}\x0a\x20*/\x0abuildAdvancedFilterCondition(filters)\x20{\x0a\x20\x20if\x20(!filters\x20||\x20!Array.isArray(filters)\x20||\x20filters.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20return\x20{\x20sql:\x20\x27\x27,\x20params:\x20[]\x20};\x0a\x20\x20}\x0a\x0a\x20\x20const\x20conditions\x20=\x20[];\x0a\x20\x20const\x20params\x20=\x20[];\x0a\x20\x20let\x20paramIndex\x20=\x20100;\x20//\x20Start\x20dari\x20100\x20agar\x20tidak\x20bentrok\x20dengan\x20param\x20lain\x0a\x0a\x20\x20for\x20(const\x20filter\x20of\x20filters)\x20{\x0a\x20\x20\x20\x20const\x20{\x20column,\x20type,\x20value,\x20value2\x20}\x20=\x20filter;\x0a\x0a\x20\x20\x20\x20if\x20(!column\x20||\x20!this.validFields.includes(column))\x20continue;\x0a\x0a\x20\x20\x20\x20switch\x20(type)\x20{\x0a\x20\x20\x20\x20\x20\x20case\x20\x27equals\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20=\x20:${paramIndex}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27not_equals\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20<>\x20:${paramIndex}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27contains\x27:\x0a\x20\x20\x20\x20\x20\x20case\x20\x27like\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`UPPER(${column})\x20LIKE\x20UPPER(:${paramIndex})`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(`%${value}%`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27not_contains\x27:\x0a\x20\x20\x20\x20\x20\x20case\x20\x27not_like\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`UPPER(${column})\x20NOT\x20LIKE\x20UPPER(:${paramIndex})`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(`%${value}%`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27starts_with\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`UPPER(${column})\x20LIKE\x20UPPER(:${paramIndex})`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(`${value}%`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27ends_with\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`UPPER(${column})\x20LIKE\x20UPPER(:${paramIndex})`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(`%${value}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27greater_than\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20>\x20:${paramIndex}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27less_than\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20<\x20:${paramIndex}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27greater_equal\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20>=\x20:${paramIndex}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27less_equal\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20<=\x20:${paramIndex}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27between\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20BETWEEN\x20:${paramIndex}\x20AND\x20:${paramIndex\x20+\x201}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value,\x20value2);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex\x20+=\x202;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27in\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(Array.isArray(value))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20inPlaceholders\x20=\x20value.map((_,\x20i)\x20=>\x20`:${paramIndex\x20+\x20i}`).join(\x27,\x20\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20IN\x20(${inPlaceholders})`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20params.push(...value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20paramIndex\x20+=\x20value.length;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27not_in\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(Array.isArray(value))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20notInPlaceholders\x20=\x20value.map((_,\x20i)\x20=>\x20`:${paramIndex\x20+\x20i}`).join(\x27,\x20\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20NOT\x20IN\x20(${notInPlaceholders})`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20params.push(...value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20paramIndex\x20+=\x20value.length;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27is_null\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20IS\x20NULL`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27is_not_null\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`${column}\x20IS\x20NOT\x20NULL`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27date_equals\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`TRUNC(${column})\x20=\x20TO_DATE(:${paramIndex},\x20\x27YYYY-MM-DD\x27)`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27date_between\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`TRUNC(${column})\x20BETWEEN\x20TO_DATE(:${paramIndex},\x20\x27YYYY-MM-DD\x27)\x20AND\x20TO_DATE(:${paramIndex\x20+\x201},\x20\x27YYYY-MM-DD\x27)`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value,\x20value2);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex\x20+=\x202;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27date_after\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`TRUNC(${column})\x20>\x20TO_DATE(:${paramIndex},\x20\x27YYYY-MM-DD\x27)`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20case\x20\x27date_before\x27:\x0a\x20\x20\x20\x20\x20\x20\x20\x20conditions.push(`TRUNC(${column})\x20<\x20TO_DATE(:${paramIndex},\x20\x27YYYY-MM-DD\x27)`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20paramIndex++;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20default:\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x0a\x0a\x20\x20if\x20(conditions.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20return\x20{\x20sql:\x20\x27\x27,\x20params:\x20[]\x20};\x0a\x20\x20}\x0a\x0a\x20\x20return\x20{\x20sql:\x20conditions.join(\x27\x20AND\x20\x27),\x20params\x20};\x0a}\x0a\x0a/**\x0a\x20*\x20Escape\x20value\x20untuk\x20Oracle\x20SQL\x20(sanitization)\x0a\x20*/\x0aescapeValue(value)\x20{\x0a\x20\x20if\x20(value\x20===\x20null\x20||\x20value\x20===\x20undefined)\x20return\x20null;\x0a\x20\x20if\x20(typeof\x20value\x20===\x20\x27number\x27)\x20return\x20value;\x0a\x20\x20return\x20String(value).replace(/\x27/g,\x20\x22\x27\x27\x22);\x0a}\x0a\x0a/**\x0a\x20*\x20Validasi\x20data\x20sebelum\x20insert/update\x0a\x20*/\x0aasync\x20validateData(data,\x20operation\x20=\x20\x27insert\x27)\x20{\x0a\x20\x20const\x20result\x20=\x20{\x0a\x20\x20\x20\x20isValid:\x20true,\x0a\x20\x20\x20\x20errors:\x20[],\x0a\x20\x20\x20\x20warnings:\x20[],\x0a\x20\x20\x20\x20sanitizedData:\x20{}\x0a\x20\x20};\x0a\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20hasFieldValidation\x20=\x20this.validationConfig\x20&&\x20Object.keys(this.validationConfig).length\x20>\x200;\x0a\x0a\x20\x20\x20\x20if\x20(hasFieldValidation)\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20Loop\x20semua\x20field\x20yang\x20ada\x20di\x20validationConfig\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20fieldName\x20in\x20this.validationConfig)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20let\x20value\x20=\x20data[fieldName];\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20config\x20=\x20this.validationConfig[fieldName];\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20constraints\x20=\x20config.constraints\x20||\x20{};\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Auto-generate\x20value\x20jika\x20autoGenerate\x20dan\x20nilai\x20kosong.\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20String\x20dan\x20uuid\x20diperlakukan\x20sama:\x20UUID\x20v7\x20via\x20uuid\x20package\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20(konsisten\x20lintas\x20dialect;\x20cocok\x20dengan\x20konvensi\x20payload\x20category.json\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20yang\x20memakai\x20type:\x20\x22string\x22\x20dengan\x20constraint\x20autoGenerate\x20+\x20primaryKey).\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(operation\x20===\x20\x27insert\x27\x20&&\x20constraints.autoGenerate\x20&&\x20(!value\x20||\x20value\x20===\x20\x27\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(config.type\x20===\x20\x27uuid\x27\x20||\x20config.type\x20===\x20\x27string\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20value\x20=\x20require(\x27uuid\x27).v7();\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20data[fieldName]\x20=\x20value;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Skip\x20validation\x20jika\x20value\x20kosong\x20dan\x20tidak\x20required\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(value\x20===\x20undefined\x20||\x20value\x20===\x20null\x20||\x20value\x20===\x20\x27\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(constraints.required)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Skip:\x20autoGenerate\x20atau\x20primaryKey\x20di\x20insert\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(operation\x20===\x20\x27insert\x27\x20&&\x20(constraints.autoGenerate\x20||\x20constraints.primaryKey))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20OK\x20—\x20akan\x20di-generate\x20otomatis\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Skip:\x20update\x20partial\x20—\x20field\x20tidak\x20dikirim\x20berarti\x20tidak\x20diubah\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20else\x20if\x20(operation\x20===\x20\x27update\x27\x20&&\x20value\x20===\x20undefined)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20OK\x20—\x20field\x20tidak\x20sedang\x20di-update\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20message\x20=\x20constraints.requiredMessage\x20||\x20`Field\x20\x27${fieldName}\x27\x20is\x20required`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20result.errors.push(message);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20result.isValid\x20=\x20false;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20continue;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20String\x20field:\x20hash\x20constraint\x20support\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(config.type\x20===\x20\x27string\x27\x20&&\x20typeof\x20value\x20===\x20\x27string\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20let\x20sanitized\x20=\x20value;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20fieldErrors\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20isHashField\x20=\x20constraints.hash\x20===\x20\x27bcrypt\x27;\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Trim\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(constraints.trim)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20sanitized\x20=\x20sanitized.trim();\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Case\x20transformation\x20(skip\x20jika\x20hash\x20field)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!isHashField)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(constraints.lowercase)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20sanitized\x20=\x20sanitized.toLowerCase();\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20if\x20(constraints.uppercase)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20sanitized\x20=\x20sanitized.toUpperCase();\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Length\x20validation\x20(validasi\x20plaintext\x20sebelum\x20hash)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(constraints.minLength\x20&&\x20sanitized.length\x20<\x20constraints.minLength)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20fieldErrors.push(constraints.minLengthMessage\x20||\x20`Field\x20\x27${fieldName}\x27\x20must\x20be\x20at\x20least\x20${constraints.minLength}\x20characters`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(constraints.maxLength\x20&&\x20!isHashField\x20&&\x20sanitized.length\x20>\x20constraints.maxLength)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20fieldErrors.push(constraints.maxLengthMessage\x20||\x20`Field\x20\x27${fieldName}\x27\x20must\x20not\x20exceed\x20${constraints.maxLength}\x20characters`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Pattern\x20validation\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(constraints.pattern)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20regex\x20=\x20new\x20RegExp(constraints.pattern);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!regex.test(sanitized))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20fieldErrors.push(constraints.patternMessage\x20||\x20`Field\x20\x27${fieldName}\x27\x20does\x20not\x20match\x20required\x20pattern`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Format\x20validation\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(constraints.format\x20===\x20\x27email\x27\x20&&\x20!/^[^\x5cs@]+@[^\x5cs@]+\x5c.[^\x5cs@]+$/.test(sanitized))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20fieldErrors.push(constraints.formatMessage\x20||\x20`Field\x20\x27${fieldName}\x27\x20has\x20invalid\x20email\x20format`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(fieldErrors.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20result.isValid\x20=\x20false;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20result.errors.push(...fieldErrors);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Hash\x20transformation\x20(setelah\x20semua\x20validation\x20pass)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(isHashField\x20&&\x20fieldErrors.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20bcrypt\x20=\x20require(\x27bcrypt\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20cost\x20=\x20constraints.hashCost\x20||\x2010;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20sanitized\x20=\x20await\x20bcrypt.hash(sanitized,\x20cost);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20result.sanitizedData[fieldName]\x20=\x20sanitized;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Non-string\x20field:\x20basic\x20sanitization\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20result.sanitizedData[fieldName]\x20=\x20value;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Validate\x20field\x20yang\x20tidak\x20ada\x20di\x20validationConfig\x20(backward\x20compatibility)\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20field\x20of\x20this.validFields)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!this.validationConfig[field]\x20&&\x20data[field]\x20!==\x20undefined\x20&&\x20data[field]\x20!==\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(typeof\x20data[field]\x20===\x20\x27string\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20result.sanitizedData[field]\x20=\x20data[field].trim().replace(/\x5c0/g,\x20\x27\x27).substring(0,\x204000);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20result.sanitizedData[field]\x20=\x20data[field];\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20Fallback:\x20Tidak\x20ada\x20fieldValidation\x20-\x20gunakan\x20generic\x20sanitization\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20field\x20of\x20this.validFields)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20value\x20=\x20data[field];\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(value\x20!==\x20undefined\x20&&\x20value\x20!==\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(typeof\x20value\x20===\x20\x27string\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20result.sanitizedData[field]\x20=\x20value.trim().replace(/\x5c0/g,\x20\x27\x27).substring(0,\x204000);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20result.sanitizedData[field]\x20=\x20value;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20result.errors.push(`Validation\x20error:\x20${error.message}`);\x0a\x20\x20\x20\x20result.isValid\x20=\x20false;\x0a\x20\x20}\x0a\x0a\x20\x20return\x20result;\x0a}\x0a\x0a/**\x0a\x20*\x20Get\x20field\x20mapping\x20information\x0a\x20*/\x0agetFieldMapping()\x20{\x0a\x20\x20return\x20{\x0a\x20\x20\x20\x20allFields:\x20this.validFields,\x0a\x20\x20\x20\x20primaryKey:\x20this.primaryKey,\x0a\x20\x20\x20\x20textFields:\x20this.validFields.filter(f\x20=>\x20f.includes(\x27name\x27)\x20||\x20f.includes(\x27nama\x27)\x20||\x20f.includes(\x27description\x27)),\x0a\x20\x20\x20\x20dateFields:\x20this.validFields.filter(f\x20=>\x20f.includes(\x27date\x27)\x20||\x20f.includes(\x27time\x27)),\x0a\x20\x20\x20\x20numericFields:\x20this.validFields.filter(f\x20=>\x20f.includes(\x27amount\x27)\x20||\x20f.includes(\x27price\x27)\x20||\x20f.includes(\x27count\x27))\x0a\x20\x20};\x0a}\x0a\x0a/**\x0a\x20*\x20Get\x20Oracle\x20connection\x20info\x20untuk\x20health\x20check\x0a\x20*/\x0aasync\x20getConnectionInfo()\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20db.executeQuery(\x27SELECT\x201\x20as\x20TEST_CON\x20FROM\x20DUAL\x27);\x0a\x20\x20\x20\x20if\x20(result\x20&&\x20result.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20{\x20connected:\x20true,\x20database:\x20\x27Oracle\x27,\x20retrievedAt:\x20new\x20Date().toISOString()\x20};\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20return\x20null;\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20return\x20{\x20connected:\x20false,\x20error:\x20error.message,\x20checkedAt:\x20new\x20Date().toISOString()\x20};\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Format\x20response\x20data\x20untuk\x20Oracle\x20(field\x20names\x20uppercase)\x0a\x20*/\x0aformatResponseData(data)\x20{\x0a\x20\x20if\x20(!data)\x20return\x20null;\x0a\x0a\x20\x20const\x20formatted\x20=\x20{};\x0a\x20\x20'+_0x5d2aff[_0x4d1639(0x201)]['map'](_0xb686bd=>_0x4d1639(0x229)+_0xb686bd+_0x4d1639(0x17c)+_0xb686bd['toUpperCase']()+';')[_0x4d1639(0x1d2)](_0x4993f4['WuMSz'])+_0x4d1639(0x1a9)+((()=>{const _0x1e2a89=_0x4d1639,_0xd581bc=_0x5d2aff[_0x1e2a89(0x175)]&&_0x5d2aff['masterDetail'][_0x1e2a89(0x19c)],_0x1824df=_0x5d2aff[_0x1e2a89(0x217)]||{};if(!_0xd581bc||!_0x1824df['createComposite']&&!_0x1824df[_0x1e2a89(0x166)]&&!_0x1824df['readComposite'])return'';const _0x1c6852=_0x5d2aff[_0x1e2a89(0x175)][_0x1e2a89(0x13f)],_0x23d4a7=_0x1c6852['split']('.')['pop'](),_0x7220d8=_0x5d2aff[_0x1e2a89(0x175)][_0x1e2a89(0x1de)],_0x57e0a8=_0x5d2aff[_0x1e2a89(0x175)][_0x1e2a89(0x1e1)]?.['primaryKey']||_0x23d4a7+_0x1e2a89(0x184),_0x5b7fd5=_0x5d2aff[_0x1e2a89(0x175)][_0x1e2a89(0x182)]||null,_0x477357=_0x5d2aff['masterDetail'][_0x1e2a89(0x1e1)]?.[_0x1e2a89(0x223)]||{},_0x42127e=_0x5d2aff[_0x1e2a89(0x175)]['detailConfig']?.[_0x1e2a89(0x145)]||null,_0x857753=[],_0x180cbe=[];for(const [_0x2b5fdd,_0x2f7874]of Object[_0x1e2a89(0x12e)](_0x477357)){if(_0x2f7874[_0x1e2a89(0x12b)]===_0x4993f4[_0x1e2a89(0x242)])_0x857753[_0x1e2a89(0x18b)](_0x2b5fdd);else{const _0x58fc7f=(_0x2f7874['formula']||'')[_0x1e2a89(0x1ac)]('*')['map'](_0x3d79a5=>_0x3d79a5[_0x1e2a89(0x1b8)]());_0x180cbe['push']({'fieldName':_0x2b5fdd,'qtyField':_0x58fc7f[0x0],'priceField':_0x58fc7f[0x1]});}}const _0x2909fb=_0x477357['total_amount']?.[_0x1e2a89(0x20f)]||'',_0x368378=_0x2909fb['split']('*')['map'](_0x22af08=>_0x22af08[_0x1e2a89(0x1b8)]()),_0x39d6e8=_0x5b7fd5?.['total_qty']?.[_0x1e2a89(0x14a)]?.[_0x1e2a89(0x138)]('items.','')||'',_0x216dee=_0x368378[0x0]||_0x39d6e8,_0x4a2b06=_0x368378[0x1]||'unit_price',_0x2b55ac=_0x180cbe['map'](_0x26f9ab=>{const _0x34d978=_0x1e2a89;return'\x20\x20\x20\x20\x20\x20//\x20Auto-calculate\x20'+_0x26f9ab[_0x34d978(0x201)]+_0x34d978(0x12c)+_0x26f9ab['qtyField']+'\x20*\x20'+_0x26f9ab['priceField']+'\x0a\x20\x20\x20\x20\x20\x20item.'+_0x26f9ab['fieldName']+'\x20=\x20(Number(item.'+_0x26f9ab['qtyField']+')\x20||\x200)\x20*\x20(Number(item.'+_0x26f9ab['priceField']+')\x20||\x200);';})['join']('\x0a');let _0x576451='';_0x1824df['createComposite']&&(_0x576451+=_0x1e2a89(0x1d9)+_0x23d4a7+'\x27;\x0a\x20\x20\x20\x20const\x20headerData\x20=\x20{\x20...data\x20};\x0a\x20\x20\x20\x20delete\x20headerData[detailKey];\x0a\x0a\x20\x20\x20\x20//\x20---\x20Hook:\x20onBeforeCompositeInsert\x20---\x0a\x20\x20\x20\x20if\x20(eventContext\x20&&\x20eventContext.componentEngine)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20_ce\x20=\x20eventContext.componentEngine;\x0a\x20\x20\x20\x20\x20\x20var\x20_CB\x20=\x20eventContext.ContextBuilder;\x0a\x20\x20\x20\x20\x20\x20var\x20_detailItems\x20=\x20data[detailKey]\x20||\x20[];\x0a\x20\x20\x20\x20\x20\x20var\x20_beforeCtx\x20=\x20_CB.buildCompositeInsertBeforeContext(headerData,\x20_detailItems,\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x5d2aff['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x1c6852+_0x1e2a89(0x198)+_0x7220d8+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20...(eventContext.additionalContext\x20||\x20{})\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20var\x20_beforeResult\x20=\x20await\x20_ce.executeOnBeforeComposite(\x27insert\x27,\x20_beforeCtx);\x0a\x20\x20\x20\x20\x20\x20if\x20(!_beforeResult.success)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20await\x20connection.rollback();\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27onBeforeCompositeInsert\x20failed:\x20\x27\x20+\x20_beforeResult.error);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Build\x20header\x20INSERT\x0a\x20\x20\x20\x20const\x20headerFields\x20=\x20[];\x0a\x20\x20\x20\x20const\x20headerValues\x20=\x20[];\x0a\x20\x20\x20\x20const\x20headerPlaceholders\x20=\x20[];\x0a\x20\x20\x20\x20let\x20idx\x20=\x201;\x0a\x20\x20\x20\x20for\x20(const\x20[key,\x20value]\x20of\x20Object.entries(headerData))\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(value\x20!==\x20undefined\x20&&\x20value\x20!==\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20headerFields.push(key);\x0a\x20\x20\x20\x20\x20\x20\x20\x20headerValues.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20headerPlaceholders.push(\x27:\x27\x20+\x20idx++);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Inject\x20audit\x20columns\x20(created_at,\x20created_by,\x20updated_at,\x20updated_by)\x20via\x20helper\x0a\x20\x20\x20\x20idx\x20=\x20this._appendCreateAuditColumns(headerFields,\x20headerValues,\x20headerPlaceholders,\x20headerData,\x20eventContext,\x20idx);\x0a\x0a\x20\x20\x20\x20const\x20insertSql\x20=\x20\x27INSERT\x20INTO\x20\x27\x20+\x20this.writeSource\x20+\x20\x27\x20(\x27\x20+\x20headerFields.join(\x27,\x20\x27)\x20+\x20\x27)\x20VALUES\x20(\x27\x20+\x20headerPlaceholders.join(\x27,\x20\x27)\x20+\x20\x27)\x27;\x0a\x20\x20\x20\x20console.log(\x27Executing\x20header\x20INSERT:\x27,\x20{\x20query:\x20insertSql,\x20values:\x20headerValues\x20});\x0a\x20\x20\x20\x20await\x20connection.execute(insertSql,\x20headerValues,\x20{\x20autoCommit:\x20false\x20});\x0a\x0a\x20\x20\x20\x20//\x20SELECT\x20back\x20inserted\x20header\x0a\x20\x20\x20\x20const\x20selectSql\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20this.primaryKey\x20+\x20\x27\x20=\x20:1\x27;\x0a\x20\x20\x20\x20const\x20headerResult\x20=\x20await\x20connection.execute(selectSql,\x20[headerData[this.primaryKey]],\x20{\x20autoCommit:\x20false,\x20outFormat:\x20oracledb.OUT_FORMAT_OBJECT\x20});\x0a\x20\x20\x20\x20const\x20insertedHeader\x20=\x20this.formatResponseData(headerResult.rows[0]);\x0a\x20\x20\x20\x20const\x20masterPkValue\x20=\x20headerData[this.primaryKey];\x0a\x0a\x20\x20\x20\x20console.log(\x27Header\x20inserted\x20successfully:\x20\x27\x20+\x20this.primaryKey\x20+\x20\x27=\x27\x20+\x20masterPkValue);\x0a\x0a\x20\x20\x20\x20//\x20Insert\x20detail\x20items\x0a\x20\x20\x20\x20const\x20insertedItems\x20=\x20[];\x0a\x20\x20\x20\x20const\x20detailTableFull\x20=\x20\x27'+_0x1c6852+_0x1e2a89(0x1f0)+_0x7220d8+_0x1e2a89(0x1f3)+_0x57e0a8+'\x27;\x0a\x20\x20\x20\x20'+(_0x857753['length']>0x0?'const\x20generatedFields\x20=\x20'+JSON[_0x1e2a89(0x1f8)](_0x857753)+';':'')+'\x0a\x0a\x20\x20\x20\x20for\x20(const\x20item\x20of\x20data[detailKey]\x20||\x20[])\x20{\x0a\x20\x20\x20\x20\x20\x20item[fk]\x20=\x20masterPkValue;\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Auto-generate\x20UUID\x20untuk\x20detail\x20PK\x20bila\x20client\x20tidak\x20menyupply.\x0a\x20\x20\x20\x20\x20\x20//\x20Oracle\x20belum\x20memakai\x20RETURNING\x20INTO\x20di\x20generator,\x20sehingga\x20generator\x0a\x20\x20\x20\x20\x20\x20//\x20perlu\x20mengetahui\x20nilai\x20PK\x20di\x20muka\x20agar\x20SELECT-back\x20dapat\x20me-retrieve\x0a\x20\x20\x20\x20\x20\x20//\x20row.\x20Cocok\x20untuk\x20kolom\x20VARCHAR2\x20yang\x20PK-nya\x20diisi\x20trigger/DEFAULT\x0a\x20\x20\x20\x20\x20\x20//\x20UUID;\x20untuk\x20IDENTITY/SEQUENCE\x20integer,\x20client\x20harus\x20mengirim\x20PK.\x0a\x20\x20\x20\x20\x20\x20if\x20(item[detailPk]\x20===\x20undefined\x20||\x20item[detailPk]\x20===\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20item[detailPk]\x20=\x20require(\x27uuid\x27).v7();\x0a\x20\x20\x20\x20\x20\x20}\x0a'+(_0x2b55ac?_0x4993f4[_0x1e2a89(0x237)]('\x0a',_0x2b55ac)+'\x0a':'')+'\x0a\x20\x20\x20\x20\x20\x20const\x20detailFields\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20const\x20detailValues\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20const\x20detailPlaceholders\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20let\x20dIdx\x20=\x201;\x0a\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20[key,\x20value]\x20of\x20Object.entries(item))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20'+(_0x857753[_0x1e2a89(0x1bc)]>0x0?_0x1e2a89(0x12d):'')+_0x1e2a89(0x23a)+_0x5d2aff[_0x1e2a89(0x255)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x1c6852+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x7220d8+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20primaryKey:\x20this.primaryKey,\x0a\x20\x20\x20\x20\x20\x20\x20\x20...(eventContext.additionalContext\x20||\x20{})\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20var\x20_afterResult\x20=\x20await\x20_ce2.executeOnAfterComposite(\x27insert\x27,\x20_afterCtx);\x0a\x20\x20\x20\x20\x20\x20if\x20(!_afterResult.success)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20await\x20connection.rollback();\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27onAfterCompositeInsert\x20failed:\x20\x27\x20+\x20_afterResult.error);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20await\x20connection.commit();\x0a\x20\x20\x20\x20console.log(\x27Transaction\x20committed\x20successfully\x27);\x0a\x0a\x20\x20\x20\x20//\x20Invalidate\x20cache\x20setelah\x20write\x20operation\x20berhasil\x0a\x20\x20\x20\x20await\x20this.invalidateCache();\x0a\x0a\x20\x20\x20\x20return\x20{\x0a\x20\x20\x20\x20\x20\x20...insertedHeader,\x0a\x20\x20\x20\x20\x20\x20[detailKey]:\x20insertedItems\x0a\x20\x20\x20\x20};\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20try\x20{\x20await\x20connection.rollback();\x20}\x20catch\x20(e)\x20{\x20/*\x20ignore\x20*/\x20}\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20createComposite:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x20finally\x20{\x0a\x20\x20\x20\x20try\x20{\x20await\x20connection.close();\x20}\x20catch\x20(e)\x20{\x20/*\x20ignore\x20*/\x20}\x0a\x20\x20}\x0a}\x0a');_0x1824df[_0x1e2a89(0x166)]&&(_0x576451+=_0x1e2a89(0x1b1)+_0x23d4a7+_0x1e2a89(0x1f4)+_0x5d2aff[_0x1e2a89(0x255)]+_0x1e2a89(0x161)+_0x1c6852+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x7220d8+_0x1e2a89(0x16b)+_0x1c6852+_0x1e2a89(0x1f0)+_0x7220d8+_0x1e2a89(0x1f3)+_0x57e0a8+_0x1e2a89(0x231)+(_0x857753['length']>0x0?_0x1e2a89(0x17d)+JSON[_0x1e2a89(0x1f8)](_0x857753)+';':'')+'\x0a\x0a\x20\x20\x20\x20const\x20detailOperations\x20=\x20data[detailKey]\x20||\x20{};\x0a\x20\x20\x20\x20const\x20{\x20insert:\x20insertItems\x20=\x20[],\x20update:\x20updateItems\x20=\x20[],\x20delete:\x20deleteItems\x20=\x20[]\x20}\x20=\x20detailOperations;\x0a\x0a\x20\x20\x20\x20const\x20deletedItems\x20=\x20[];\x0a\x20\x20\x20\x20const\x20updatedItems\x20=\x20[];\x0a\x20\x20\x20\x20const\x20insertedItems\x20=\x20[];\x0a\x0a\x20\x20\x20\x20//\x201.\x20DELETE\x20operations\x0a\x20\x20\x20\x20for\x20(const\x20item\x20of\x20deleteItems)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(!item[detailPk])\x20throw\x20new\x20Error(\x27Missing\x20\x27\x20+\x20detailPk\x20+\x20\x27\x20in\x20delete\x20operation\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20delSql\x20=\x20\x27DELETE\x20FROM\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20detailPk\x20+\x20\x27\x20=\x20:1\x27;\x0a\x20\x20\x20\x20\x20\x20await\x20connection.execute(delSql,\x20[item[detailPk]],\x20{\x20autoCommit:\x20false\x20});\x0a\x20\x20\x20\x20\x20\x20deletedItems.push(item);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20console.log(\x27Deleted\x20\x27\x20+\x20deletedItems.length\x20+\x20\x27\x20detail\x20item(s)\x27);\x0a\x0a\x20\x20\x20\x20//\x202.\x20UPDATE\x20operations\x0a\x20\x20\x20\x20for\x20(const\x20item\x20of\x20updateItems)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(!item[detailPk])\x20throw\x20new\x20Error(\x27Missing\x20\x27\x20+\x20detailPk\x20+\x20\x27\x20in\x20update\x20operation\x27);\x0a'+(_0x2b55ac?_0x4993f4['yIFTy']('\x0a'+_0x2b55ac,'\x0a'):'')+'\x0a\x20\x20\x20\x20\x20\x20const\x20dFields\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20const\x20dValues\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20let\x20dIdx\x20=\x201;\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20[key,\x20value]\x20of\x20Object.entries(item))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(key\x20===\x20detailPk)\x20continue;\x0a\x20\x20\x20\x20\x20\x20\x20\x20'+(_0x857753['length']>0x0?_0x1e2a89(0x12d):'')+_0x1e2a89(0x24b)+(_0x2b55ac?_0x4993f4['WYgMg']('\x0a',_0x2b55ac)+'\x0a':'')+_0x1e2a89(0x24f)+(_0x857753[_0x1e2a89(0x1bc)]>0x0?_0x1e2a89(0x12d):'')+'\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(value\x20!==\x20undefined\x20&&\x20value\x20!==\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20dFields.push(key);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20dValues.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20dPlaceholders.push(\x27:\x27\x20+\x20dIdx++);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Inject\x20audit\x20columns\x20ke\x20detail\x20INSERT\x20via\x20helper\x0a\x20\x20\x20\x20\x20\x20dIdx\x20=\x20this._appendCreateAuditColumns(dFields,\x20dValues,\x20dPlaceholders,\x20item,\x20eventContext,\x20dIdx);\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20dInsertSql\x20=\x20\x27INSERT\x20INTO\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20(\x27\x20+\x20dFields.join(\x27,\x20\x27)\x20+\x20\x27)\x20VALUES\x20(\x27\x20+\x20dPlaceholders.join(\x27,\x20\x27)\x20+\x20\x27)\x27;\x0a\x20\x20\x20\x20\x20\x20await\x20connection.execute(dInsertSql,\x20dValues,\x20{\x20autoCommit:\x20false\x20});\x0a\x20\x20\x20\x20\x20\x20insertedItems.push(item);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20console.log(\x27Inserted\x20\x27\x20+\x20insertedItems.length\x20+\x20\x27\x20new\x20detail\x20item(s)\x27);\x0a\x0a\x20\x20\x20\x20//\x20Get\x20all\x20current\x20detail\x20items\x0a\x20\x20\x20\x20const\x20allItemsSql\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20fk\x20+\x20\x27\x20=\x20:1\x20ORDER\x20BY\x20line_number\x27;\x0a\x20\x20\x20\x20const\x20allItemsResult\x20=\x20await\x20connection.execute(allItemsSql,\x20[primaryKeyValue],\x20{\x20autoCommit:\x20false,\x20outFormat:\x20oracledb.OUT_FORMAT_OBJECT\x20});\x0a\x20\x20\x20\x20const\x20allItems\x20=\x20allItemsResult.rows\x20||\x20[];\x0a'+(_0x5b7fd5?_0x1e2a89(0x135)+JSON[_0x1e2a89(0x1f8)](_0x5b7fd5)+';\x0a\x20\x20\x20\x20const\x20recalcFields\x20=\x20[];\x0a\x20\x20\x20\x20const\x20recalcValues\x20=\x20[];\x0a\x20\x20\x20\x20let\x20recalcIdx\x20=\x201;\x0a\x0a\x20\x20\x20\x20if\x20(calculations.total_items)\x20{\x0a\x20\x20\x20\x20\x20\x20recalcFields.push(\x27total_items\x20=\x20:\x27\x20+\x20recalcIdx++);\x0a\x20\x20\x20\x20\x20\x20recalcValues.push(allItems.length);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(calculations.total_qty\x20&&\x20calculations.total_qty.source)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20qtyField\x20=\x20calculations.total_qty.source.replace(\x27items.\x27,\x20\x27\x27).toUpperCase();\x0a\x20\x20\x20\x20\x20\x20const\x20totalQty\x20=\x20allItems.reduce(function(sum,\x20item)\x20{\x20return\x20sum\x20+\x20(Number(item[qtyField])\x20||\x200);\x20},\x200);\x0a\x20\x20\x20\x20\x20\x20recalcFields.push(\x27total_qty\x20=\x20:\x27\x20+\x20recalcIdx++);\x0a\x20\x20\x20\x20\x20\x20recalcValues.push(totalQty);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20'+(_0x216dee?'if\x20(calculations.total_amount)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20totalAmount\x20=\x20allItems.reduce(function(sum,\x20item)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20qty\x20=\x20Number(item.'+_0x216dee['toUpperCase']()+'\x20||\x20item.'+_0x216dee+')\x20||\x200;\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20price\x20=\x20Number(item.'+_0x4a2b06['toUpperCase']()+'\x20||\x20item.'+_0x4a2b06+_0x1e2a89(0x1a7):_0x1e2a89(0x13d))+'\x0a\x0a\x20\x20\x20\x20if\x20(recalcFields.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20Inject\x20audit\x20columns\x20(updated_at,\x20updated_by)\x20ke\x20recalc\x20UPDATE\x20via\x20helper\x0a\x20\x20\x20\x20\x20\x20recalcIdx\x20=\x20this._appendUpdateAuditColumns(recalcFields,\x20recalcValues,\x20data,\x20eventContext,\x20recalcIdx);\x0a\x20\x20\x20\x20\x20\x20recalcValues.push(primaryKeyValue);\x0a\x20\x20\x20\x20\x20\x20var\x20recalcSql\x20=\x20\x27UPDATE\x20\x27\x20+\x20this.writeSource\x20+\x20\x27\x20SET\x20\x27\x20+\x20recalcFields.join(\x27,\x20\x27)\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20this.primaryKey\x20+\x20\x27\x20=\x20:\x27\x20+\x20recalcIdx;\x0a\x20\x20\x20\x20\x20\x20console.log(\x27Recalculating\x20header\x20totals:\x27,\x20{\x20query:\x20recalcSql,\x20values:\x20recalcValues\x20});\x0a\x20\x20\x20\x20\x20\x20await\x20connection.execute(recalcSql,\x20recalcValues,\x20{\x20autoCommit:\x20false\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20var\x20recalcResult\x20=\x20await\x20connection.execute(selectSql,\x20[primaryKeyValue],\x20{\x20autoCommit:\x20false,\x20outFormat:\x20oracledb.OUT_FORMAT_OBJECT\x20});\x0a\x20\x20\x20\x20\x20\x20updatedHeader\x20=\x20this.formatResponseData(recalcResult.rows[0]);\x0a\x20\x20\x20\x20}\x0a':'')+'\x0a\x20\x20\x20\x20//\x20---\x20Hook:\x20onAfterCompositeUpdate\x20---\x0a\x20\x20\x20\x20if\x20(eventContext\x20&&\x20eventContext.componentEngine)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20_ce2\x20=\x20eventContext.componentEngine;\x0a\x20\x20\x20\x20\x20\x20var\x20_CB2\x20=\x20eventContext.ContextBuilder;\x0a\x20\x20\x20\x20\x20\x20var\x20_afterCtx\x20=\x20_CB2.buildCompositeUpdateAfterContext(headerData,\x20oldData,\x20updatedHeader,\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20inserted:\x20insertedItems,\x0a\x20\x20\x20\x20\x20\x20\x20\x20updated:\x20updatedItems,\x0a\x20\x20\x20\x20\x20\x20\x20\x20deleted:\x20deletedItems\x0a\x20\x20\x20\x20\x20\x20},\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x5d2aff['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x1c6852+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x7220d8+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20primaryKey:\x20this.primaryKey,\x0a\x20\x20\x20\x20\x20\x20\x20\x20...(eventContext.additionalContext\x20||\x20{})\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20var\x20_afterResult\x20=\x20await\x20_ce2.executeOnAfterComposite(\x27update\x27,\x20_afterCtx);\x0a\x20\x20\x20\x20\x20\x20if\x20(!_afterResult.success)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20await\x20connection.rollback();\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27onAfterCompositeUpdate\x20failed:\x20\x27\x20+\x20_afterResult.error);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20await\x20connection.commit();\x0a\x20\x20\x20\x20console.log(\x27Transaction\x20committed\x20successfully\x27);\x0a\x0a\x20\x20\x20\x20//\x20Invalidate\x20cache\x20setelah\x20write\x20operation\x20berhasil\x0a\x20\x20\x20\x20await\x20this.invalidateCache();\x0a\x0a\x20\x20\x20\x20return\x20{\x0a\x20\x20\x20\x20\x20\x20...updatedHeader,\x0a\x20\x20\x20\x20\x20\x20[detailKey]:\x20allItems,\x0a\x20\x20\x20\x20\x20\x20_operations:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20deleted:\x20deletedItems.length,\x0a\x20\x20\x20\x20\x20\x20\x20\x20updated:\x20updatedItems.length,\x0a\x20\x20\x20\x20\x20\x20\x20\x20inserted:\x20insertedItems.length\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20};\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20try\x20{\x20await\x20connection.rollback();\x20}\x20catch\x20(e)\x20{\x20/*\x20ignore\x20*/\x20}\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20updateComposite:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x20finally\x20{\x0a\x20\x20\x20\x20try\x20{\x20await\x20connection.close();\x20}\x20catch\x20(e)\x20{\x20/*\x20ignore\x20*/\x20}\x0a\x20\x20}\x0a}\x0a');if(_0x1824df['readComposite']){let _0xd40b9a;if(_0x42127e&&typeof _0x42127e==='string'&&_0x42127e['startsWith'](_0x1e2a89(0x15c))){const _0x3d5572=_0x42127e[_0x1e2a89(0x138)](_0x4993f4[_0x1e2a89(0x13c)],''),_0x1d76c5=_0x3d5572[_0x1e2a89(0x1ac)]('/')['pop']();_0xd40b9a=_0x1e2a89(0x16c)+_0x1d76c5+'\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(fs.existsSync(detailQueryFilePath))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailSql\x20=\x20fs.readFileSync(detailQueryFilePath,\x20\x27utf8\x27).trim();\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(`Detail\x20query\x20file\x20not\x20found:\x20${detailQueryFilePath}`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27Failed\x20to\x20load\x20detail\x20query\x20file:\x20\x27\x20+\x20error.message);\x0a\x20\x20\x20\x20}';}else _0x42127e?_0xd40b9a='const\x20detailSql\x20=\x20`'+_0x42127e[_0x1e2a89(0x138)](/\$1/g,':1')+'`;':_0xd40b9a=_0x1e2a89(0x129)+_0x1c6852+_0x1e2a89(0x148)+_0x7220d8+_0x1e2a89(0x1d0);_0x576451+=_0x1e2a89(0x1cc)+_0x23d4a7+_0x1e2a89(0x231)+_0xd40b9a+_0x1e2a89(0x159);}return _0x576451;})())+'\x0a}\x0a\x0amodule.exports\x20=\x20new\x20'+_0x155924+'Model();';}function createOracleSubmoduleTemplate(_0x21a0e6,_0x3a1620,_0x41fed5){const _0x4b6b4e=a0_0x1e54,_0x5db881={'SURBm':function(_0x9641d6,_0x374ba9){return _0x9641d6(_0x374ba9);},'isCPG':_0x4b6b4e(0x17e),'SeeJT':'null','yUEtV':_0x4b6b4e(0x228),'CLxFw':'let\x20componentEngine\x20=\x20null;\x0alet\x20ContextBuilder\x20=\x20null;\x0a','aymmB':'unit_price','aCQcc':function(_0x199907,_0x276e61){return _0x199907!==_0x276e61;}},_0x496ae9=_0x5db881[_0x4b6b4e(0x258)](toCamelCase,_0x3a1620)+_0x5db881[_0x4b6b4e(0x1af)],_0x1bd2f6=_0x41fed5['primaryKey']||'id',_0x41f224=new Date()[_0x4b6b4e(0x22a)](),_0x38a3a7=Object['entries'](_0x41fed5['action']||{})[_0x4b6b4e(0x13e)](([,_0x247e0a])=>_0x247e0a)['map'](([_0xf7a2e6])=>_0xf7a2e6),_0x4987c2=_0x41fed5[_0x4b6b4e(0x13b)]&&Array['isArray'](_0x41fed5['components'])&&_0x41fed5[_0x4b6b4e(0x13b)][_0x4b6b4e(0x1bc)]>0x0,_0x509708=_0x41fed5[_0x4b6b4e(0x15e)]||'SELECT\x20'+_0x41fed5['fieldName']['join'](',\x20')+_0x4b6b4e(0x153)+_0x41fed5[_0x4b6b4e(0x255)],_0x5b2f06=_0x509708[_0x4b6b4e(0x138)](/\\/g,'\x5c\x5c')['replace'](/'/g,'\x5c\x27'),_0x5de46d=JSON[_0x4b6b4e(0x1f8)](_0x41fed5[_0x4b6b4e(0x201)]),_0x51df69=_0x41fed5['columnFormats']?JSON[_0x4b6b4e(0x1f8)](_0x41fed5['columnFormats']):_0x5db881['SeeJT'],_0x4fd741=_0x41fed5['fieldLabels']?JSON['stringify'](_0x41fed5['fieldLabels']):_0x4b6b4e(0x183),_0x3866ad=_0x41fed5['action']&&_0x41fed5[_0x4b6b4e(0x217)]['import']?{'enabled':!![],'upsertKeys':(_0x41fed5[_0x4b6b4e(0x1f6)]||{})[_0x4b6b4e(0x132)]||[_0x1bd2f6],'upsertStrategy':(_0x41fed5['importConfig']||{})['upsertStrategy']||_0x5db881[_0x4b6b4e(0x142)],'requiredFields':(_0x41fed5[_0x4b6b4e(0x1f6)]||{})[_0x4b6b4e(0x218)]||[],'validations':(_0x41fed5['importConfig']||{})[_0x4b6b4e(0x192)]||{},'lookupFields':(_0x41fed5['importConfig']||{})[_0x4b6b4e(0x204)]||{},'excludeFromImport':(_0x41fed5[_0x4b6b4e(0x1f6)]||{})[_0x4b6b4e(0x151)]||[],'chunkSize':(_0x41fed5[_0x4b6b4e(0x1f6)]||{})[_0x4b6b4e(0x1d5)]||0x64}:null,_0x3be1b5=_0x3866ad?JSON['stringify'](_0x3866ad):_0x5db881['SeeJT'],_0x1c6bc4=_0x41fed5['action']&&_0x41fed5['action']['export']?'\x27'+_0x5b2f06+'\x27':_0x4b6b4e(0x183),_0x3b2c0e=_0x41fed5[_0x4b6b4e(0x217)]&&_0x41fed5[_0x4b6b4e(0x217)]['adjust']?_0x41fed5[_0x4b6b4e(0x1fc)]||{}:null,_0x1c36f4=_0x3b2c0e?JSON[_0x4b6b4e(0x1f8)](_0x3b2c0e):_0x5db881['SeeJT'],_0x32fcbc=_0x41fed5['fieldPolicy']?',\x20'+JSON['stringify'](_0x41fed5[_0x4b6b4e(0x216)]):'';let _0x38183a=_0x5db881['CLxFw'];_0x4987c2&&(_0x38183a+='componentEngine\x20=\x20require(\x27@restforgejs/platform/src/utils/component-engine\x27).componentEngine;\x0a',_0x38183a+='ContextBuilder\x20=\x20require(\x27@restforgejs/platform/src/utils/context-builder\x27);\x0a');const _0x51b60e=_0x41fed5['requestScope']?'\x0a\x20\x20\x20\x20//\x20Request\x20scope\x20ownership\x20verification\x20(Layer\x201\x20RLS)\x0a\x20\x20\x20\x20if\x20(req._requestScope)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20scopeCheck\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x1fa)+_0x3a1620+'\x20data\x20not\x20found\x20or\x20access\x20denied\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a':'',_0x30c578=_0x41fed5[_0x4b6b4e(0x20c)]?'\x0a\x20\x20\x20\x20//\x20Request\x20scope\x20ownership\x20verification\x20(Layer\x201\x20RLS)\x0a\x20\x20\x20\x20if\x20(req._requestScope\x20&&\x20result.data\x20&&\x20result.data.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20record\x20=\x20result.data[0];\x0a\x20\x20\x20\x20\x20\x20if\x20(record[req._requestScope.column]\x20!==\x20undefined\x20&&\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20String(record[req._requestScope.column])\x20!==\x20String(req._requestScope.value))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(404).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x3a1620+'\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a':'',_0x312fa6=_0x41fed5[_0x4b6b4e(0x20c)]?'\x0a\x20\x20\x20\x20//\x20Request\x20scope\x20ownership\x20verification\x20(Layer\x201\x20RLS)\x20—\x20master\x20record\x0a\x20\x20\x20\x20if\x20(req._requestScope)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20scopeCheck\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x139)+_0x1bd2f6+_0x4b6b4e(0x261)+_0x1bd2f6+_0x4b6b4e(0x1bd):'';let _0x4a3fbd=_0x4b6b4e(0x150)+_0x496ae9+'\x20=\x20require(\x27../../models/'+_0x21a0e6+'/'+_0x3a1620+_0x4b6b4e(0x14c)+_0x38183a+_0x4b6b4e(0x1ca)+toPascalCase(_0x3a1620)+_0x4b6b4e(0x1bf)+_0x41f224+'\x0a*\x0a*\x20Oracle-optimized\x20endpoints\x20untuk\x20'+_0x3a1620+'\x0a*\x20Actions:\x20'+_0x38a3a7['join'](',\x20')+'\x0a*\x20Table:\x20'+_0x41fed5[_0x4b6b4e(0x255)]+_0x4b6b4e(0x160)+_0x1bd2f6+_0x4b6b4e(0x185)+_0x41fed5['tableName']+_0x4b6b4e(0x21d)+_0x5de46d+',\x0a\x20\x20exportQuery:\x20'+_0x1c6bc4+',\x0a\x20\x20columnFormats:\x20'+_0x51df69+_0x4b6b4e(0x179)+_0x4fd741+_0x4b6b4e(0x1ff)+_0x3be1b5+_0x4b6b4e(0x221)+_0x1c36f4+',\x0a\x20\x20uploadConfig:\x20'+(_0x41fed5['uploadConfig']?JSON['stringify'](_0x41fed5['uploadConfig']):'null')+',\x0a\x20\x20requestScope:\x20'+(_0x41fed5[_0x4b6b4e(0x20c)]?JSON[_0x4b6b4e(0x1f8)](_0x41fed5[_0x4b6b4e(0x20c)]):_0x4b6b4e(0x183))+'\x0a};\x0a'+(_0x4987c2?_0x4b6b4e(0x1a5)+JSON[_0x4b6b4e(0x1f8)](_0x41fed5[_0x4b6b4e(0x13b)])+'\x20};\x0acomponentEngine.loadConfigurationFromObject(_componentPayload).then(result\x20=>\x20{\x0a\x20\x20if\x20(result.success)\x20{\x0a\x20\x20\x20\x20console.log(`Component\x20configuration\x20loaded\x20for\x20'+_0x21a0e6+'/'+_0x3a1620+':\x20${result.componentsLoaded}\x20components`);\x0a\x20\x20}\x0a}).catch(err\x20=>\x20{\x0a\x20\x20console.error(`Failed\x20to\x20load\x20component\x20configuration\x20for\x20'+_0x21a0e6+'/'+_0x3a1620+':`,\x20err.message);\x0a});\x0a':'')+_0x4b6b4e(0x1aa)+_0x1bd2f6+_0x4b6b4e(0x164);_0x4a3fbd+=buildRequestScopeMiddleware(_0x41fed5,_0x3a1620);_0x41fed5[_0x4b6b4e(0x217)]&&_0x41fed5['action']['datatables']&&(_0x4a3fbd+='//\x20POST\x20/api/'+_0x21a0e6+'/'+_0x3a1620+'/datatables\x20-\x20Data\x20untuk\x20DataTables\x0arouter.post(\x27/datatables\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20options\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20searchValue:\x20req.body.search?.value\x20||\x20req.body.searchValue\x20||\x20req.body.search_value\x20||\x20\x27\x27,\x0a\x20\x20\x20\x20\x20\x20searchBy:\x20req.body.searchBy\x20||\x20req.body.search_by\x20||\x20\x27all\x27,\x0a\x20\x20\x20\x20\x20\x20perPage:\x20Math.min(parseInt(req.body.length\x20||\x20req.body.pagination?.perpage\x20||\x2010,\x2010),\x201000),\x0a\x20\x20\x20\x20\x20\x20start:\x20Math.max(parseInt(req.body.start\x20||\x200,\x2010),\x200),\x0a\x20\x20\x20\x20\x20\x20draw:\x20req.body.draw\x20||\x20\x271\x27\x0a\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20//\x20Handle\x20sort_columns\x0a\x20\x20\x20\x20if\x20(req.body.sort_columns\x20&&\x20Array.isArray(req.body.sort_columns)\x20&&\x20req.body.sort_columns.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20options.sort_columns\x20=\x20req.body.sort_columns.map(item\x20=>\x20({\x0a\x20\x20\x20\x20\x20\x20\x20\x20column:\x20item.column,\x0a\x20\x20\x20\x20\x20\x20\x20\x20direction:\x20(item.direction\x20||\x20\x27ASC\x27).toUpperCase()\x0a\x20\x20\x20\x20\x20\x20}));\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Fallback:\x20Handle\x20DataTables\x20standard\x20format\x20(order[0][column]\x20dan\x20order[0][dir])\x0a\x20\x20\x20\x20if\x20(req.body[\x27order[0][column]\x27]\x20!==\x20undefined)\x20{\x0a\x20\x20\x20\x20\x20\x20options[\x27order[0][column]\x27]\x20=\x20req.body[\x27order[0][column]\x27];\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(req.body[\x27order[0][dir]\x27]\x20!==\x20undefined)\x20{\x0a\x20\x20\x20\x20\x20\x20options[\x27order[0][dir]\x27]\x20=\x20req.body[\x27order[0][dir]\x27];\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Handle\x20filters\x20dengan\x20sanitasi\x0a\x20\x20\x20\x20if\x20(req.body.filters\x20&&\x20typeof\x20req.body.filters\x20===\x20\x27object\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20sanitizedFilters\x20=\x20{};\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20[key,\x20value]\x20of\x20Object.entries(req.body.filters))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(value\x20!==\x20null\x20&&\x20value\x20!==\x20undefined\x20&&\x20value\x20!==\x20\x27\x27\x20&&\x20value\x20!==\x20\x27all\x27\x20&&\x20value\x20!==\x20\x27-\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20sanitizedFilters[key]\x20=\x20value;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20if\x20(Object.keys(sanitizedFilters).length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20options.filters\x20=\x20sanitizedFilters;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Support\x20WHERE\x20conditions\x0a\x20\x20\x20\x20if\x20(req.body.where)\x20{\x0a\x20\x20\x20\x20\x20\x20options.where\x20=\x20req.body.where;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Advanced\x20filters\x20support\x0a\x20\x20\x20\x20if\x20(req.body.advanced_filters\x20&&\x20Array.isArray(req.body.advanced_filters))\x20{\x0a\x20\x20\x20\x20\x20\x20options.advancedFilters\x20=\x20req.body.advanced_filters;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Gunakan\x20model\x20untuk\x20mendapatkan\x20data\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x1df)+_0x3a1620+_0x4b6b4e(0x128)+_0x3a1620+_0x4b6b4e(0x23f));_0x41fed5[_0x4b6b4e(0x217)]&&_0x41fed5['action']['lookup']&&(_0x4a3fbd+='//\x20GET\x20/api/'+_0x21a0e6+'/'+_0x3a1620+_0x4b6b4e(0x147)+_0x496ae9+_0x4b6b4e(0x1a3)+_0x496ae9+_0x4b6b4e(0x202)+_0x496ae9+'.getLookupData(search);\x0a\x20\x20\x20\x20const\x20lookupTime\x20=\x20Date.now()\x20-\x20startTime;\x0a\x0a\x20\x20\x20\x20console.log(`[ORA-LKP]\x20${oraRequestId}\x20found\x20${list.length}\x20results\x20in\x20${lookupTime}ms`);\x0a\x0a\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20count:\x20list.length,\x0a\x20\x20\x20\x20\x20\x20data:\x20list,\x0a\x20\x20\x20\x20\x20\x20search:\x20search,\x0a\x20\x20\x20\x20\x20\x20_oracle:\x20{\x20requestId:\x20oraRequestId,\x20queryTime:\x20lookupTime,\x20timestamp:\x20new\x20Date().toISOString()\x20}\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(`[ORA-LKP]\x20Error\x20${oraRequestId}:`,\x20error);\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20looking\x20up\x20'+_0x3a1620+_0x4b6b4e(0x260));_0x41fed5[_0x4b6b4e(0x217)]&&_0x41fed5[_0x4b6b4e(0x217)]['lookup']&&(_0x4a3fbd+='//\x20POST\x20/api/'+_0x21a0e6+'/'+_0x3a1620+'/lookup\x20-\x20Oracle\x20Static\x20Lookup\x0arouter.post(\x27/lookup\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20const\x20oraRequestId\x20=\x20req.oraRequestId;\x0a\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20requestMode\x20=\x20req.headers[\x27x-request-mode\x27];\x0a\x0a\x20\x20\x20\x20if\x20(requestMode\x20!==\x20\x27static\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20Request\x20Mode\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27X-Request-Mode\x20header\x20must\x20be\x20set\x20to\x20static\x20for\x20POST\x20lookup\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20console.log(`[ORA-LKP]\x20${oraRequestId}\x20static\x20lookup:`,\x20JSON.stringify(req.body,\x20null,\x202));\x0a\x0a\x20\x20\x20\x20const\x20startTime\x20=\x20Date.now();\x0a\x20\x20\x20\x20let\x20list;\x0a\x0a\x20\x20\x20\x20if\x20(req.body.where)\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20New\x20format\x20dengan\x20where\x20clause\x20+\x20optional\x20select\x20dan\x20order\x0a\x20\x20\x20\x20\x20\x20list\x20=\x20await\x20'+_0x496ae9+'.getLookupDataWithFilter(req.body);\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20Legacy\x20format\x20dengan\x20selected_tag\x0a\x20\x20\x20\x20\x20\x20const\x20selectedTag\x20=\x20req.body.selected_tag\x20||\x20\x27\x27;\x0a\x20\x20\x20\x20\x20\x20list\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x170)+_0x3a1620+'\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a');_0x41fed5['action']&&_0x41fed5[_0x4b6b4e(0x217)][_0x4b6b4e(0x20a)]&&(_0x4a3fbd+='//\x20POST\x20/api/'+_0x21a0e6+'/'+_0x3a1620+'/create\x20-\x20Oracle\x20Insert\x0arouter.post(\x27/create\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20Object.keys(req.body).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20data\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x496ae9+_0x4b6b4e(0x1e0)+_0x496ae9+'.validateData(req.body,\x20\x27insert\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(!validation.isValid)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20errors:\x20validation.errors,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20req.body\x20=\x20{\x20...req.body,\x20...validation.sanitizedData\x20};\x0a\x20\x20\x20\x20}\x0a\x0a'+(_0x4987c2?_0x4b6b4e(0x1ce)+_0x41fed5[_0x4b6b4e(0x255)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20user_id:\x20req.headers[\x27user-id\x27]\x20||\x20req.headers[\x27x-user-id\x27]\x20||\x20\x27system\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20options:\x20req.bodyOptions\x20||\x20{},\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20requestId:\x20req.id\x20||\x20null\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20result\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x149)+_0x496ae9+_0x4b6b4e(0x1cf):'\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20result\x20=\x20await\x20'+_0x496ae9+'.addData(req.body,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20});\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[FALLBACK]\x20INSERT\x20completed\x20without\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[FALLBACK]\x20INSERT\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a')+'\x0a\x20\x20\x20\x20console.log(`'+_0x3a1620+'\x20data\x20added\x20successfully:\x20${result.'+_0x1bd2f6+_0x4b6b4e(0x19d)+_0x3a1620+_0x4b6b4e(0x17a)+_0x3a1620+_0x4b6b4e(0x220)+_0x3a1620+'\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a');_0x41fed5[_0x4b6b4e(0x217)]&&_0x41fed5[_0x4b6b4e(0x217)]['update']&&(_0x4a3fbd+='//\x20POST\x20/api/'+_0x21a0e6+'/'+_0x3a1620+_0x4b6b4e(0x133)+_0x1bd2f6+'\x27;\x0a\x20\x20\x20\x20if\x20(!req.body[primaryKey])\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Missing\x20required\x20field\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20`Primary\x20key\x20(${primaryKey})\x20is\x20required\x20for\x20update`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a'+_0x51b60e+'\x0a\x20\x20\x20\x20//\x20Validasi\x20data\x20dengan\x20model\x20jika\x20tersedia\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x496ae9+_0x4b6b4e(0x1e0)+_0x496ae9+_0x4b6b4e(0x1a1)+(_0x4987c2?_0x4b6b4e(0x248)+_0x41fed5['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20user_id:\x20req.headers[\x27user-id\x27]\x20||\x20req.headers[\x27x-user-id\x27]\x20||\x20\x27system\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20options:\x20req.bodyOptions\x20||\x20{}\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20responseData\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x1e5)+_0x32fcbc+_0x4b6b4e(0x1c5):_0x4b6b4e(0x1a8)+_0x496ae9+'.updateData(req.body,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20}'+_0x32fcbc+_0x4b6b4e(0x140))+'\x0a\x20\x20\x20\x20//\x20Log\x20successful\x20operation\x0a\x20\x20\x20\x20console.log(`'+_0x3a1620+_0x4b6b4e(0x137)+_0x1bd2f6+_0x4b6b4e(0x21f)+_0x1bd2f6+'\x27]}`);\x0a\x0a\x20\x20\x20\x20return\x20res.status(200).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x3a1620+'\x20data\x20successfully\x20updated\x27,\x0a\x20\x20\x20\x20\x20\x20data:\x20responseData,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20mengupdate\x20data\x20'+_0x3a1620+_0x4b6b4e(0x20e)+_0x3a1620+_0x4b6b4e(0x1c9)+_0x3a1620+_0x4b6b4e(0x23f));_0x41fed5['action']&&_0x41fed5['action']['adjust']&&(_0x4a3fbd+=_0x4b6b4e(0x144)+_0x21a0e6+'/'+_0x3a1620+'/adjust\x20-\x20Oracle\x20Adjust\x20(atomic\x20increment/decrement)\x0arouter.post(\x27/adjust\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Validasi\x20payload\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20Object.keys(req.body).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20primary\x20key\x0a\x20\x20\x20\x20const\x20primaryKey\x20=\x20\x27'+_0x1bd2f6+_0x4b6b4e(0x1f2)+_0x51b60e+'\x0a\x20\x20\x20\x20const\x20adjustConfig\x20=\x20componentConfig.adjustConfig\x20||\x20{};\x0a\x20\x20\x20\x20let\x20responseData\x20=\x20null;\x0a\x0a'+(_0x4987c2?'\x0a\x20\x20\x20\x20//\x20Integrated\x20transaction\x20dengan\x20event\x20lifecycle\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x41fed5['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20user_id:\x20req.headers[\x27user-id\x27]\x20||\x20req.headers[\x27x-user-id\x27]\x20||\x20\x27system\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20options:\x20req.bodyOptions\x20||\x20{}\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20responseData\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x169)+_0x32fcbc+_0x4b6b4e(0x24a):'\x0a\x20\x20\x20\x20//\x20Fallback:\x20mode\x20tanpa\x20events\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20responseData\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x157)+_0x32fcbc+_0x4b6b4e(0x21c))+_0x4b6b4e(0x1f9)+_0x3a1620+_0x4b6b4e(0x1b5)+_0x1bd2f6+'=${req.body[\x27'+_0x1bd2f6+'\x27]}`);\x0a\x0a\x20\x20\x20\x20return\x20res.status(200).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x3a1620+'\x20data\x20successfully\x20adjusted\x27,\x0a\x20\x20\x20\x20\x20\x20data:\x20responseData,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20mengadjust\x20data\x20'+_0x3a1620+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.statusCode\x20===\x20403)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(403).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Pro\x20Feature\x20Required\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20upgrade:\x20\x27https://restforge.dev/pricing\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(error.message.includes(\x27constraint\x20violation\x27)\x20||\x20error.message.includes(\x27below\x20minimum\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(409).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Constraint\x20violation\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(error.message.includes(\x27not\x20configured\x20for\x20adjustment\x27)\x20||\x20error.message.includes(\x27is\x20required\x20for\x20adjust\x27)\x20||\x20error.message.includes(\x27must\x20be\x20a\x20non-zero\x20number\x27)\x20||\x20error.message.includes(\x27not\x20a\x20valid\x20field\x27)\x20||\x20error.message.includes(\x27must\x20not\x20be\x20empty\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20error\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(error.message\x20===\x20\x27Data\x20tidak\x20ditemukan\x27\x20||\x20error.message.includes(\x27not\x20found\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(404).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x3a1620+'\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Oracle\x20unique\x20constraint\x20violation\x20(ORA-00001).\x20Adjust\x20beroperasi\x20dengan\x0a\x20\x20\x20\x20//\x20UPDATE\x20atomic\x20sehingga\x20ORA-00001\x20jarang\x20terpicu,\x20namun\x20handler\x20ini\x20tetap\x0a\x20\x20\x20\x20//\x20disertakan\x20untuk\x20parity\x20dengan\x20MySQL\x20(ER_DUP_ENTRY)\x20dan\x20PostgreSQL\x20(23505).\x0a\x20\x20\x20\x20if\x20(error.errorNum\x20===\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(409).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Duplicate\x20entry\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27A\x20record\x20with\x20this\x20value\x20already\x20exists\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20adjusting\x20'+_0x3a1620+_0x4b6b4e(0x23f));if(_0x41fed5['action']&&_0x41fed5[_0x4b6b4e(0x217)]['workflow']){const _0x50ebe8=_0x41fed5['workflow']?JSON['stringify'](_0x41fed5[_0x4b6b4e(0x1d8)]):'{}';_0x4a3fbd+='//\x20POST\x20/api/'+_0x21a0e6+'/'+_0x3a1620+'/change-status\x20-\x20Oracle\x20Change\x20status\x20'+_0x3a1620+'\x0arouter.post(\x27/change-status\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20Object.keys(req.body).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20primaryKey\x20=\x20\x27'+_0x1bd2f6+'\x27;\x0a\x20\x20\x20\x20const\x20recordId\x20=\x20req.body[primaryKey]\x20||\x20req.body.id;\x0a\x20\x20\x20\x20if\x20(!recordId)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Missing\x20required\x20field\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20`Primary\x20key\x20(${primaryKey})\x20or\x20id\x20is\x20required\x20for\x20change-status`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(!req.body.status)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Missing\x20required\x20field\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27status\x20is\x20required\x20for\x20change-status\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a'+_0x51b60e['replace'](/req\.body\[primaryKey\]/g,'recordId')+_0x4b6b4e(0x191)+_0x50ebe8+_0x4b6b4e(0x22e)+_0x21a0e6+_0x4b6b4e(0x25e)+(_0x4987c2?'\x0a\x20\x20\x20\x20//\x20Integrated\x20transaction\x20dengan\x20event\x20lifecycle\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x41fed5[_0x4b6b4e(0x255)]+_0x4b6b4e(0x186)+_0x496ae9+_0x4b6b4e(0x245):'\x0a\x20\x20\x20\x20//\x20Fallback:\x20tanpa\x20component\x20engine\x20events\x20(tetap\x20forward\x20authHeader\x20untuk\x20hook\x20JWT\x20forwarding)\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20user_id:\x20req.headers[\x27user-id\x27]\x20||\x20req.headers[\x27x-user-id\x27]\x20||\x20req.body.updated_by\x20||\x20\x27system\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20requestId:\x20req.id\x20||\x20null,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20authHeader:\x20req.headers.authorization\x20||\x20null\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20result\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x1be))+_0x4b6b4e(0x22c)+_0x3a1620+'\x20status\x20changed:\x20${result.workflow.previousStatus}\x20→\x20${result.workflow.newStatus}`);\x0a\x0a\x20\x20\x20\x20return\x20res.status(200).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20message:\x20`Status\x20changed\x20from\x20${result.workflow.previousStatus}\x20to\x20${result.workflow.newStatus}`,\x0a\x20\x20\x20\x20\x20\x20data:\x20result.data,\x0a\x20\x20\x20\x20\x20\x20workflow:\x20result.workflow,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20change-status\x20'+_0x3a1620+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20//\x20Status\x20transition\x20not\x20allowed\x0a\x20\x20\x20\x20if\x20(error.statusCode\x20===\x20422\x20||\x20error.message.includes(\x27Cannot\x20change\x20status\x27)\x20||\x20error.message.includes(\x27No\x20transitions\x20defined\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(422).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Status\x20transition\x20not\x20allowed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Concurrent\x20modification\x20(optimistic\x20concurrency\x20check\x20via\x20status\x20guard\x20clause)\x0a\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27WORKFLOW_CONCURRENT_MODIFICATION\x27\x20||\x20error.statusCode\x20===\x20409)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(409).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Concurrent\x20modification\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validation\x20errors\x0a\x20\x20\x20\x20if\x20(error.message.includes(\x27is\x20required\x20for\x27)\x20||\x20error.message.includes(\x27is\x20not\x20a\x20valid\x20field\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20error\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Hook\x20execution\x20failed\x0a\x20\x20\x20\x20if\x20(error.message.includes(\x27hook\x20failed\x27)\x20||\x20error.message.includes(\x27onBefore\x27)\x20||\x20error.message.includes(\x27onAfter\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(502).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Workflow\x20hook\x20failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Record\x20not\x20found\x0a\x20\x20\x20\x20if\x20(error.message\x20===\x20\x27Record\x20not\x20found\x27\x20||\x20error.message.includes(\x27not\x20found\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(404).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x3a1620+'\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Lock\x20acquisition\x20failed\x0a\x20\x20\x20\x20if\x20(error.message.includes(\x27Failed\x20to\x20acquire\x20lock\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(409).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Resource\x20busy\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20changing\x20status\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a';}if(_0x41fed5[_0x4b6b4e(0x217)]&&_0x41fed5[_0x4b6b4e(0x217)][_0x4b6b4e(0x178)]){const _0x4040c3=_0x41fed5[_0x4b6b4e(0x20b)]?JSON['stringify'](_0x41fed5[_0x4b6b4e(0x20b)]):'{}';_0x4a3fbd+='//\x20POST\x20/api/'+_0x21a0e6+'/'+_0x3a1620+_0x4b6b4e(0x1a4)+_0x4040c3+';\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20'+_0x496ae9+'.aggregateData(req.body\x20||\x20{},\x20aggregateConfig);\x0a\x0a\x20\x20\x20\x20return\x20res.status(200).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20data:\x20result,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20mengagregasi\x20data\x20'+_0x3a1620+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.statusCode\x20===\x20403)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(403).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Pro\x20Feature\x20Required\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20upgrade:\x20\x27https://restforge.dev/pricing\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(error.statusCode\x20===\x20400\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27not\x20a\x20valid\x20field\x27)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27Invalid\x20aggregate\x20function\x27)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27Invalid\x20alias\x27)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27only\x20allowed\x20with\x20COUNT\x27)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27not\x20defined\x20in\x20aggregate\x20config\x27)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27no\x20join\x20definitions\x20found\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20error\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20aggregating\x20'+_0x3a1620+_0x4b6b4e(0x23f);}_0x41fed5[_0x4b6b4e(0x217)]&&_0x41fed5['action']['delete']&&(_0x4a3fbd+='//\x20POST\x20/api/'+_0x21a0e6+'/'+_0x3a1620+'/delete\x20-\x20Oracle\x20Delete\x0arouter.post(\x27/delete\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Validasi\x20request\x20body\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20Object.keys(req.body).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(!req.body.where)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Missing\x20required\x20field\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20request\x20format:\x20where\x20parameter\x20is\x20required\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20example:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22where\x22:\x20[{\x20\x22key\x22:\x20\x22id\x22,\x20\x22value\x22:\x20\x22your-id-value\x22\x20}]\x0a\x20\x20\x20\x20\x20\x20\x20\x20},\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20format\x20where\x0a\x20\x20\x20\x20if\x20(!Array.isArray(req.body.where)\x20&&\x20!req.body.where.conditions)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20where\x20format\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20where\x20format\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20example:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22where\x22:\x20[\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20\x22key\x22:\x20\x22id\x22,\x20\x22value\x22:\x20\x22your-id-value\x22\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20]\x0a\x20\x20\x20\x20\x20\x20\x20\x20},\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20let\x20responseData\x20=\x20null;\x0a\x0a\x20\x20\x20\x20//\x20Cek\x20apakah\x20data\x20exist\x20sebelum\x20delete\x20dan\x20ambil\x20old\x20data\x20untuk\x20event\x20lifecycle\x0a\x20\x20\x20\x20//\x20Menggunakan\x20SELECT\x20*\x20dari\x20tabel\x20utama\x20(tanpa\x20explicit\x20select)\x20karena\x20fieldName\x0a\x20\x20\x20\x20//\x20bisa\x20mengandung\x20kolom\x20dari\x20JOIN\x20(mis.\x20city_name)\x20yang\x20tidak\x20ada\x20di\x20tabel\x20utama\x0a\x20\x20\x20\x20if\x20(req.body.where\x20&&\x20Array.isArray(req.body.where)\x20&&\x20req.body.where.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20firstCondition\x20=\x20req.body.where[0];\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20existingData\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x163)+_0x3a1620+_0x4b6b4e(0x23b)+(_0x4987c2?'\x0a\x20\x20\x20\x20//\x20Integrated\x20transaction\x20dengan\x20event\x20lifecycle\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x41fed5[_0x4b6b4e(0x255)]+_0x4b6b4e(0x13a)+_0x496ae9+_0x4b6b4e(0x243):_0x4b6b4e(0x1a8)+_0x496ae9+'.deleteData(req.body,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20});\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[FALLBACK]\x20DELETE\x20completed\x20without\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[FALLBACK]\x20DELETE\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a')+'\x0a\x20\x20\x20\x20//\x20Log\x20successful\x20operation\x0a\x20\x20\x20\x20console.log(`'+_0x3a1620+'\x20data\x20deleted\x20successfully`);\x0a\x0a\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20...responseData,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20menghapus\x20data\x20'+_0x3a1620+_0x4b6b4e(0x18e)+_0x3a1620+'\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a');_0x41fed5['action']&&_0x41fed5['action']['first']&&(_0x4a3fbd+=_0x4b6b4e(0x144)+_0x21a0e6+'/'+_0x3a1620+_0x4b6b4e(0x1da)+_0x1bd2f6+_0x4b6b4e(0x207)+_0x1bd2f6+_0x4b6b4e(0x1ed)+JSON[_0x4b6b4e(0x1f8)](_0x41fed5['fieldName'])+_0x4b6b4e(0x176)+_0x496ae9+'.getData(getPayload);\x0a'+_0x30c578+_0x4b6b4e(0x194)+_0x3a1620+_0x4b6b4e(0x14d)+_0x3a1620+_0x4b6b4e(0x23f));_0x41fed5['action']&&_0x41fed5['action']['read']&&(_0x4a3fbd+='//\x20POST\x20/api/'+_0x21a0e6+'/'+_0x3a1620+_0x4b6b4e(0x143)+JSON['stringify'](_0x41fed5[_0x4b6b4e(0x201)]||[])+';\x0a\x20\x20\x20\x20let\x20select\x20=\x20null;\x0a\x20\x20\x20\x20if\x20(req.body.select\x20&&\x20Array.isArray(req.body.select))\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20invalidFields\x20=\x20req.body.select.filter(field\x20=>\x20!validFields.includes(field));\x0a\x20\x20\x20\x20\x20\x20if\x20(invalidFields.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20select\x20fields\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20field(s):\x20\x27\x20+\x20invalidFields.join(\x27,\x20\x27),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20validFields:\x20validFields,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20select\x20=\x20req.body.select;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20options\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20searchValue,\x0a\x20\x20\x20\x20\x20\x20searchBy,\x0a\x20\x20\x20\x20\x20\x20sort_columns,\x0a\x20\x20\x20\x20\x20\x20where:\x20where,\x0a\x20\x20\x20\x20\x20\x20select:\x20select\x0a\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20if\x20(paginate)\x20{\x0a\x20\x20\x20\x20\x20\x20options.page\x20=\x20page;\x0a\x20\x20\x20\x20\x20\x20options.perPage\x20=\x20perPage;\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20options.limit\x20=\x20limit;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20'+_0x496ae9+'.getList(options);\x0a\x0a\x20\x20\x20\x20//\x20Format\x20response\x20berdasarkan\x20mode\x0a\x20\x20\x20\x20if\x20(paginate)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20\x20\x20data:\x20result.data,\x0a\x20\x20\x20\x20\x20\x20\x20\x20count:\x20result.data\x20?\x20result.data.length\x20:\x200,\x0a\x20\x20\x20\x20\x20\x20\x20\x20pagination:\x20result.pagination,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Data\x20retrieved\x20successfully\x27\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20\x20\x20data:\x20result.data,\x0a\x20\x20\x20\x20\x20\x20\x20\x20count:\x20result.data\x20?\x20result.data.length\x20:\x200\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20'+_0x3a1620+'\x20list:\x27,\x20error);\x0a\x20\x20\x20\x20const\x20statusCode\x20=\x20error.statusCode\x20||\x20500;\x0a\x20\x20\x20\x20return\x20res.status(statusCode).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20statusCode\x20===\x20400\x20?\x20\x27Bad\x20Request\x27\x20:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20statusCode\x20===\x20400\x20?\x20error.message\x20:\x20\x27An\x20error\x20occurred\x20while\x20fetching\x20'+_0x3a1620+'\x20list\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a');if(_0x41fed5['masterDetail']&&_0x41fed5['masterDetail']['enabled']){const _0x238872=_0x41fed5['masterDetail']['detailTable']['split']('.')['pop'](),_0x8a1e9d=_0x3a1620['replace'](/-/g,'_'),_0x5e50b5=_0x41fed5['masterDetail'][_0x4b6b4e(0x182)]||null,_0x38799e=_0x41fed5['masterDetail']?.['detailConfig']?.[_0x4b6b4e(0x223)]?.[_0x4b6b4e(0x1d1)]?.[_0x4b6b4e(0x20f)]||'',_0x5c12d6=_0x38799e[_0x4b6b4e(0x1ac)]('*')['map'](_0x45a977=>_0x45a977[_0x4b6b4e(0x1b8)]()),_0x587258=_0x5e50b5?.[_0x4b6b4e(0x14b)]?.['source']?.['replace'](_0x4b6b4e(0x1d7),'')||'',_0xbcb79e=_0x5c12d6[0x0]||_0x587258,_0x5c0e0d=_0x5c12d6[0x1]||_0x5db881[_0x4b6b4e(0x1dd)];if(_0x41fed5['action']&&_0x41fed5[_0x4b6b4e(0x217)][_0x4b6b4e(0x244)]){const _0x27664d=_0x5e50b5?_0x4b6b4e(0x1b7)+JSON[_0x4b6b4e(0x1f8)](_0x5e50b5)+_0x4b6b4e(0x162)+_0x238872+_0x4b6b4e(0x1ef)+_0x238872+'.reduce(function(sum,\x20item)\x20{\x20return\x20sum\x20+\x20(Number(item[qtyField])\x20||\x200);\x20},\x200);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20'+(_0xbcb79e?_0x4b6b4e(0x174)+_0x238872+'.reduce(function(sum,\x20item)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20qty\x20=\x20Number(item.'+_0xbcb79e+')\x20||\x200;\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20price\x20=\x20Number(item.'+_0x5c0e0d+_0x4b6b4e(0x200):'//\x20WARNING:\x20headerCalculations.total_amount\x20skipped\x20—\x20no\x20qty\x20field\x20configured')+'\x0a\x20\x20\x20\x20console.log(\x27Calculated\x20totals:\x27,\x20{\x20total_items:\x20data.total_items,\x20total_qty:\x20data.total_qty,\x20total_amount:\x20data.total_amount\x20});\x0a\x20\x20\x20\x20':'';_0x4a3fbd+=_0x4b6b4e(0x144)+_0x21a0e6+'/'+_0x3a1620+_0x4b6b4e(0x24d)+_0x3a1620+_0x4b6b4e(0x1dc)+_0x8a1e9d+_0x4b6b4e(0x211)+_0x8a1e9d+'\x22\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20data\x20=\x20req.body.'+_0x8a1e9d+';\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20dan\x20sanitasi\x20header\x20data\x20dengan\x20constraint\x20(termasuk\x20hash)\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x496ae9+_0x4b6b4e(0x253)+_0x238872+_0x4b6b4e(0x1e7)+_0x496ae9+'.validateData(headerDataForValidation,\x20\x27insert\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(!validation.isValid)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20errors:\x20validation.errors,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20Object.assign(data,\x20validation.sanitizedData);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(!data.'+_0x238872+'\x20||\x20!Array.isArray(data.'+_0x238872+')\x20||\x20data.'+_0x238872+_0x4b6b4e(0x21a)+_0x238872+_0x4b6b4e(0x1d4)+_0x27664d+'\x0a\x0a\x20\x20\x20\x20//\x20Build\x20eventContext\x20untuk\x20composite\x20hooks\x0a\x20\x20\x20\x20var\x20eventContext\x20=\x20null;\x0a\x20\x20\x20\x20if\x20(componentEngine\x20&&\x20ContextBuilder)\x20{\x0a\x20\x20\x20\x20\x20\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x41fed5['tableName']+_0x4b6b4e(0x196)+_0x41fed5['masterDetail']['detailTable']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x41fed5[_0x4b6b4e(0x175)][_0x4b6b4e(0x1de)]+_0x4b6b4e(0x20d)+_0x496ae9+'.createComposite(data,\x20eventContext);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0x3a1620+'\x20composite\x20create\x20successful\x27);\x0a\x0a\x20\x20\x20\x20return\x20res.status(201).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x3a1620+_0x4b6b4e(0x21b)+_0x3a1620+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.errorNum\x20===\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(409).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Duplicate\x20entry\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27A\x20record\x20with\x20this\x20value\x20already\x20exists\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(error.errorNum\x20===\x202291)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Foreign\x20key\x20constraint\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Referenced\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20creating\x20'+_0x3a1620+_0x4b6b4e(0x23f);}_0x41fed5['action']&&_0x41fed5[_0x4b6b4e(0x217)][_0x4b6b4e(0x166)]&&(_0x4a3fbd+=_0x4b6b4e(0x144)+_0x21a0e6+'/'+_0x3a1620+_0x4b6b4e(0x131)+_0x3a1620+_0x4b6b4e(0x1e6)+_0x8a1e9d+_0x4b6b4e(0x211)+_0x8a1e9d+_0x4b6b4e(0x1ab)+_0x8a1e9d+';\x0a\x0a\x20\x20\x20\x20if\x20(!data.'+_0x1bd2f6+_0x4b6b4e(0x17b)+_0x1bd2f6+_0x4b6b4e(0x257)+_0x312fa6+'\x0a\x20\x20\x20\x20//\x20Validasi\x20dan\x20sanitasi\x20header\x20data\x20dengan\x20constraint\x20(termasuk\x20hash)\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x496ae9+'.validateData\x20===\x20\x27function\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20headerDataForValidation\x20=\x20Object.assign({},\x20data);\x0a\x20\x20\x20\x20\x20\x20delete\x20headerDataForValidation.'+_0x238872+_0x4b6b4e(0x1e7)+_0x496ae9+_0x4b6b4e(0x19a)+_0x238872+')\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(typeof\x20data.'+_0x238872+'\x20!==\x20\x27object\x27\x20||\x20Array.isArray(data.'+_0x238872+'))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Property\x20\x22'+_0x238872+'\x22\x20must\x20be\x20an\x20object\x20with\x20structure\x20{insert:\x20[],\x20update:\x20[],\x20delete:\x20[]}\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Build\x20eventContext\x20untuk\x20composite\x20hooks\x0a\x20\x20\x20\x20var\x20eventContext\x20=\x20null;\x0a\x20\x20\x20\x20if\x20(componentEngine\x20&&\x20ContextBuilder)\x20{\x0a\x20\x20\x20\x20\x20\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x41fed5[_0x4b6b4e(0x255)]+_0x4b6b4e(0x196)+_0x41fed5[_0x4b6b4e(0x175)]['detailTable']+_0x4b6b4e(0x232)+_0x41fed5['masterDetail']['foreignKey']+_0x4b6b4e(0x20d)+_0x496ae9+_0x4b6b4e(0x1ae)+_0x3a1620+'\x20composite\x20update\x20successful:\x20'+_0x1bd2f6+_0x4b6b4e(0x19b)+_0x1bd2f6+_0x4b6b4e(0x199)+_0x3a1620+_0x4b6b4e(0x23e)+_0x3a1620+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.message\x20===\x20\x27Record\x20not\x20found\x27\x20||\x20error.message.includes(\x27not\x20found\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(404).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x3a1620+'\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(error.errorNum\x20===\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(409).json({\x20success:\x20false,\x20error:\x20\x27Duplicate\x20entry\x27,\x20message:\x20\x27A\x20record\x20with\x20this\x20value\x20already\x20exists\x27,\x20timestamp:\x20new\x20Date().toISOString()\x20});\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(error.errorNum\x20===\x202291)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x20success:\x20false,\x20error:\x20\x27Foreign\x20key\x20constraint\x27,\x20message:\x20\x27Referenced\x20data\x20not\x20found\x27,\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x20timestamp:\x20new\x20Date().toISOString()\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20updating\x20'+_0x3a1620+_0x4b6b4e(0x23f)),_0x41fed5[_0x4b6b4e(0x217)]&&_0x41fed5[_0x4b6b4e(0x217)][_0x4b6b4e(0x1f1)]&&(_0x4a3fbd+=_0x4b6b4e(0x144)+_0x21a0e6+'/'+_0x3a1620+'/read-composite\x20-\x20Oracle\x20Read\x20header\x20with\x20detail\x20items\x0arouter.post(\x27/read-composite\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20console.log(\x27Request\x20body\x20'+_0x3a1620+_0x4b6b4e(0x1f7)+_0x1bd2f6+'\x22,\x20\x22value\x22:\x20\x22your-id-value\x22\x20}]\x20},\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20result\x20=\x20await\x20'+_0x496ae9+_0x4b6b4e(0x195)+_0x3a1620+_0x4b6b4e(0x17f)+_0x3a1620+_0x4b6b4e(0x14e)+_0x3a1620+'\x20composite\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a');}if(_0x41fed5['action']?.['info']!==![]){const _0xdef1b0={'datatables':!!_0x41fed5[_0x4b6b4e(0x217)]?.[_0x4b6b4e(0x254)],'read':!!_0x41fed5[_0x4b6b4e(0x217)]?.[_0x4b6b4e(0x226)],'first':!!_0x41fed5[_0x4b6b4e(0x217)]?.[_0x4b6b4e(0x247)],'create':!!_0x41fed5[_0x4b6b4e(0x217)]?.[_0x4b6b4e(0x20a)],'update':!!_0x41fed5['action']?.['update'],'delete':!!_0x41fed5['action']?.[_0x4b6b4e(0x24e)],'lookup':!!_0x41fed5['action']?.[_0x4b6b4e(0x1c0)],'export':!!_0x41fed5['action']?.['export'],'import':!!_0x41fed5[_0x4b6b4e(0x217)]?.[_0x4b6b4e(0x219)],'info':_0x5db881['aCQcc'](_0x41fed5[_0x4b6b4e(0x217)]?.['info'],![])};_0x4a3fbd+='//\x20Oracle\x20endpoint\x20information\x20—\x20self-documenting\x20API\x0arouter.get(\x27/info\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20actions\x20=\x20'+JSON['stringify'](_0xdef1b0)+_0x4b6b4e(0x1ad)+_0x496ae9+'.getModelInfo(actions);\x0a\x0a\x20\x20\x20\x20res.json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20endpoint:\x20\x27'+_0x3a1620+_0x4b6b4e(0x16a)+_0x21a0e6+_0x4b6b4e(0x1ec)+_0x41f224+'\x27,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Oracle\x20info\x20error:\x27,\x20error);\x0a\x20\x20\x20\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Info\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20fetching\x20endpoint\x20info\x27,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});';}else _0x4a3fbd+='//\x20Endpoint\x20/info\x20dinonaktifkan\x20via\x20action.info\x20=\x20false\x0a';return _0x4a3fbd+=_0x4b6b4e(0x19e)+_0x496ae9+_0x4b6b4e(0x1cd)+_0x3a1620+'\x27,\x0a\x20\x20\x20\x20\x20\x20database:\x20\x27oracle\x27,\x0a\x20\x20\x20\x20\x20\x20connection:\x20connectionInfo\x20?\x20\x27active\x27\x20:\x20\x27unknown\x27,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20res.status(503).json({\x0a\x20\x20\x20\x20\x20\x20status:\x20\x27unhealthy\x27,\x0a\x20\x20\x20\x20\x20\x20endpoint:\x20\x27'+_0x3a1620+'\x27,\x0a\x20\x20\x20\x20\x20\x20database:\x20\x27oracle\x27,\x0a\x20\x20\x20\x20\x20\x20error:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a',_0x4a3fbd+='module.exports\x20=\x20router;',_0x4a3fbd;}module[a0_0x364e7c(0x1c8)]={'createOracleMainModuleTemplate':createOracleMainModuleTemplate,'createOracleModelTemplate':createOracleModelTemplate,'createOracleSubmoduleTemplate':createOracleSubmoduleTemplate};