@restforgejs/platform 4.2.8 → 4.3.2
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.
- package/SECURITY.md +83 -4
- package/bin/sdf-tools.exe +0 -0
- package/build-info.json +2 -2
- package/cli/consumer-deploy.js +1 -1
- package/cli/consumer.js +1 -1
- package/generators/cli/dashboard/create.js +4 -1
- package/generators/cli/endpoint/create.js +1 -1
- package/generators/cli/key/generate.js +2 -1
- package/generators/cli/key/revoke.js +2 -1
- package/generators/cli/payload/diff.js +3 -2
- package/generators/cli/payload/generate.js +3 -2
- package/generators/cli/payload/sync.js +3 -2
- package/generators/cli/payload/validate.js +3 -2
- package/generators/cli/processor/create.js +14 -3
- package/generators/cli/project/delete.js +2 -1
- package/generators/cli/query/validate.js +3 -2
- package/generators/cli/schema/apply.js +3 -2
- package/generators/cli/schema/describe.js +3 -2
- package/generators/cli/schema/diff.js +3 -2
- package/generators/cli/schema/introspect.js +3 -2
- package/generators/cli/schema/list.js +3 -2
- package/generators/cli/schema/migrate.js +3 -2
- package/generators/lib/migration/audit-table-runner.js +213 -215
- package/generators/lib/payload/payload-runner.js +1 -1
- package/generators/lib/templates/dashboard-catalog.js +1 -437
- package/generators/lib/templates/db-connection-env.js +1 -212
- package/generators/lib/templates/dbschema-catalog.js +1 -489
- package/generators/lib/templates/field-validation-catalog.js +1 -531
- package/generators/lib/templates/mysql-template.js +1 -3863
- package/generators/lib/templates/oracle-template.js +1 -3915
- package/generators/lib/templates/postgres-template.js +1 -5838
- package/generators/lib/templates/query-declarative-catalog.js +1 -199
- package/generators/lib/templates/sqlite-template.js +1 -3440
- package/generators/lib/utils/env-manager.js +6 -0
- package/generators/lib/utils/path-validator.js +71 -0
- package/generators/lib/validators/payload-validator.js +1 -2
- package/integrity-manifest.json +28 -10
- package/package.json +11 -3
- package/scripts/verify-integrity.js +1 -1
- package/server.js +1 -1
- package/src/components/handlers/adjust_handler.js +1 -1
- package/src/components/handlers/audit_handler.js +1 -1
- package/src/components/handlers/delete_handler.js +1 -1
- package/src/components/handlers/export_handler.js +1 -1
- package/src/components/handlers/import_handler.js +1 -1
- package/src/components/handlers/insert_handler.js +1 -1
- package/src/components/handlers/update_handler.js +1 -1
- package/src/components/handlers/upload_handler.js +1 -1
- package/src/components/handlers/workflow_handler.js +1 -1
- package/src/components/integrations/webhook.js +1 -1
- package/src/consumers/baseConsumer.js +1 -1
- package/src/consumers/declarativeMapper.js +1 -1
- package/src/consumers/handlers/apiHandler.js +1 -1
- package/src/consumers/handlers/consoleHandler.js +1 -1
- package/src/consumers/handlers/databaseHandler.js +1 -1
- package/src/consumers/handlers/index.js +1 -1
- package/src/consumers/handlers/kafkaHandler.js +1 -1
- package/src/consumers/index.js +1 -1
- package/src/consumers/messageTransformer.js +1 -1
- package/src/consumers/validator.js +1 -1
- package/src/core/db/dialect/base-dialect.js +1 -1
- package/src/core/db/dialect/index.js +1 -1
- package/src/core/db/dialect/mysql-dialect.js +1 -1
- package/src/core/db/dialect/oracle-dialect.js +1 -1
- package/src/core/db/dialect/postgres-dialect.js +1 -1
- package/src/core/db/dialect/sqlite-dialect.js +1 -1
- package/src/core/db/flatten-helper.js +1 -1
- package/src/core/db/query-builder-error.js +1 -1
- package/src/core/db/query-builder.js +1 -1
- package/src/core/db/relation-helper.js +1 -1
- package/src/core/handlers/delete_handler.js +1 -1
- package/src/core/handlers/insert_handler.js +1 -1
- package/src/core/handlers/update_handler.js +1 -1
- package/src/core/models/base-model.js +1 -1
- package/src/core/utils/cache-manager.js +1 -1
- package/src/core/utils/component-engine.js +1 -1
- package/src/core/utils/context-builder.js +1 -1
- package/src/core/utils/datetime-formatter.js +1 -1
- package/src/core/utils/datetime-parser.js +1 -1
- package/src/core/utils/db.js +1 -1
- package/src/core/utils/logger.js +1 -1
- package/src/core/utils/payload-loader.js +1 -1
- package/src/core/utils/security-checks.js +1 -1
- package/src/middleware/body-options.js +1 -1
- package/src/middleware/cors.js +1 -1
- package/src/middleware/idempotency.js +1 -1
- package/src/middleware/rate-limiter.js +1 -1
- package/src/middleware/request-logger.js +1 -1
- package/src/middleware/security-headers.js +1 -1
- package/src/models/base-model-mysql.js +1 -1
- package/src/models/base-model-oracle.js +1 -1
- package/src/models/base-model-sqlite.js +1 -1
- package/src/models/base-model.js +1 -1
- package/src/pro/caching/redis-client.js +1 -1
- package/src/pro/caching/redis-helper.js +1 -1
- package/src/pro/consumers/baseConsumer.js +1 -1
- package/src/pro/consumers/declarativeMapper.js +1 -1
- package/src/pro/consumers/handlers/apiHandler.js +1 -1
- package/src/pro/consumers/handlers/consoleHandler.js +1 -1
- package/src/pro/consumers/handlers/databaseHandler.js +1 -1
- package/src/pro/consumers/handlers/index.js +1 -1
- package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
- package/src/pro/consumers/index.js +1 -1
- package/src/pro/consumers/messageTransformer.js +1 -1
- package/src/pro/consumers/validator.js +1 -1
- package/src/pro/database/base-model-mysql.js +1 -1
- package/src/pro/database/base-model-oracle.js +1 -1
- package/src/pro/database/base-model-sqlite.js +1 -1
- package/src/pro/database/db-mysql.js +1 -1
- package/src/pro/database/db-oracle.js +1 -1
- package/src/pro/database/db-sqlite.js +1 -1
- package/src/pro/excel/excel-generator.js +1 -1
- package/src/pro/excel/excel-parser.js +1 -1
- package/src/pro/excel/export-service.js +1 -1
- package/src/pro/excel/export_handler.js +1 -1
- package/src/pro/excel/import-service.js +1 -1
- package/src/pro/excel/import-validator.js +1 -1
- package/src/pro/excel/import_handler.js +1 -1
- package/src/pro/excel/upsert-builder.js +1 -1
- package/src/pro/idgen/idgen-routes.js +1 -1
- package/src/pro/integrations/lookup-resolver.js +1 -1
- package/src/pro/integrations/upload-handler-v2.js +1 -1
- package/src/pro/integrations/upload-handler.js +1 -1
- package/src/pro/integrations/webhook.js +1 -1
- package/src/pro/locking/lock-routes.js +1 -1
- package/src/pro/locking/resource-lock-manager.js +1 -1
- package/src/pro/messaging/kafkaConsumerService.js +1 -1
- package/src/pro/messaging/kafkaService.js +1 -1
- package/src/pro/messaging/messagehubService.js +1 -1
- package/src/pro/messaging/rabbitmqService.js +1 -1
- package/src/pro/scheduler/job-manager.js +1 -1
- package/src/pro/scheduler/job-routes.js +1 -1
- package/src/pro/scheduler/job-validator.js +1 -1
- package/src/pro/storage/base-storage-provider.js +1 -1
- package/src/pro/storage/file-metadata-helper.js +1 -1
- package/src/pro/storage/index.js +1 -1
- package/src/pro/storage/local-storage-provider.js +1 -1
- package/src/pro/storage/s3-storage-provider.js +1 -1
- package/src/pro/storage/upload-cleanup-job.js +1 -1
- package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
- package/src/pro/storage/upload-pending-tracker.js +1 -1
- package/src/pro/websocket/broadcast-helper.js +1 -1
- package/src/pro/websocket/index.js +1 -1
- package/src/pro/websocket/livesync-server.js +1 -1
- package/src/pro/websocket/ws-broadcaster.js +1 -1
- package/src/services/export-service.js +1 -1
- package/src/services/import-service.js +1 -1
- package/src/services/kafkaConsumerService.js +1 -1
- package/src/services/kafkaService.js +1 -1
- package/src/services/messagehubService.js +1 -1
- package/src/services/rabbitmqService.js +1 -1
- package/src/utils/cache-invalidation-registry.js +1 -1
- package/src/utils/cache-manager.js +1 -1
- package/src/utils/component-engine.js +1 -1
- package/src/utils/config-extractor.js +1 -1
- package/src/utils/consumerLogger.js +1 -1
- package/src/utils/context-builder.js +1 -1
- package/src/utils/dashboard-helpers.js +1 -1
- package/src/utils/dateHelper.js +1 -1
- package/src/utils/datetime-formatter.js +1 -1
- package/src/utils/datetime-parser.js +1 -1
- package/src/utils/db-bootstrap.js +1 -1
- package/src/utils/db-mysql.js +1 -1
- package/src/utils/db-oracle.js +1 -1
- package/src/utils/db-sqlite.js +1 -1
- package/src/utils/db.js +1 -1
- package/src/utils/demo-generator.js +1 -1
- package/src/utils/excel-generator.js +1 -1
- package/src/utils/excel-parser.js +1 -1
- package/src/utils/file-watcher.js +1 -1
- package/src/utils/id-generator.js +1 -1
- package/src/utils/idempotency-manager.js +1 -1
- package/src/utils/import-validator.js +1 -1
- package/src/utils/license-client.js +1 -1
- package/src/utils/lock-manager.js +1 -1
- package/src/utils/logger.js +1 -1
- package/src/utils/lookup-resolver.js +1 -1
- package/src/utils/payload-loader.js +1 -1
- package/src/utils/processor-response.js +1 -1
- package/src/utils/rabbitmq.js +1 -1
- package/src/utils/redis-client.js +1 -1
- package/src/utils/redis-helper.js +1 -1
- package/src/utils/request-scope.js +1 -1
- package/src/utils/security-checks.js +1 -1
- package/src/utils/service-resolver.js +1 -1
- package/src/utils/shutdown-coordinator.js +1 -1
- package/src/utils/trusted-keys.js +1 -1
- package/src/utils/upload-handler.js +1 -1
- package/src/utils/upsert-builder.js +1 -1
- package/src/utils/workflow-hook-executor.js +1 -1
- package/generators/metadata/global.json +0 -58
- package/generators/metadata/test-mysql-workbench.json +0 -118
- package/generators/metadata/test-mysql.json +0 -56
- package/generators/metadata/test-oracle-workbench.json +0 -118
- package/generators/metadata/test-oracle.json +0 -56
- package/generators/metadata/test-pg-workbench.json +0 -118
- package/generators/metadata/test-pg.json +0 -56
- package/generators/scripts/obfuscate-source.js +0 -356
- package/generators/scripts/validate-catalog.js +0 -430
- package/generators/scripts/validate-dbschema-catalog.js +0 -708
- package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js +0 -944
- package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
- package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory.js +0 -336
- package/generators/tests/baseline/oracle/mini_inventory_item/src/models/mini-inventory/item.js +0 -1002
- package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
- package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory.js +0 -336
- package/generators/tests/baseline/postgres/mini_inventory_item/src/models/mini-inventory/item.js +0 -1333
- package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory/item.js +0 -1173
- package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory.js +0 -496
- package/generators/tests/fixtures/payloads/custom-sensitive.json +0 -27
- package/generators/tests/fixtures/payloads/dynamic-search-optout.json +0 -23
- package/generators/tests/fixtures/payloads/login-with-password.json +0 -22
- package/generators/tests/fixtures/payloads/order-process.json +0 -52
- package/generators/tests/fixtures/payloads/with-inline-sql.json +0 -26
- package/generators/tests/integration-tahap4b/README.md +0 -145
- package/generators/tests/integration-tahap4b/run-concurrent.js +0 -77
- package/generators/tests/integration-tahap4b/seed.sql +0 -53
- package/generators/tests/integration-tahap4b/verify.sql +0 -110
- package/generators/tests/unit/cli/create-dashboard.test.js +0 -505
- package/generators/tests/unit/cli/create-processor.test.js +0 -319
- package/generators/tests/unit/cli/dispatch-dashboard.test.js +0 -149
- package/generators/tests/unit/lib/dashboard-generator.test.js +0 -895
- package/generators/tests/unit/lib/dashboard-validator.test.js +0 -354
- package/generators/tests/unit/lib/dbschema-kit/apply-executor.test.js +0 -437
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-introspect.test.js +0 -393
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-generate-ddl.test.js +0 -104
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-init.test.js +0 -119
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-list.test.js +0 -48
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-migrate.test.js +0 -175
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-validate.test.js +0 -102
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-models.test.js +0 -43
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/all-schemas-listing.js +0 -84
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/connection-error.js +0 -13
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/empty.js +0 -12
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/multi-schema.js +0 -124
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/single-schema-inventory.js +0 -64
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/two-tables.js +0 -66
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/connection-error.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/partial.js +0 -29
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/rollback.js +0 -26
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/success.js +0 -43
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/audit/events.js +0 -18
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/inventory/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/users.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/connection.test.js +0 -112
- package/generators/tests/unit/lib/dbschema-kit/ddl-generator.test.js +0 -205
- package/generators/tests/unit/lib/dbschema-kit/define-model.test.js +0 -56
- package/generators/tests/unit/lib/dbschema-kit/dialect/index.test.js +0 -46
- package/generators/tests/unit/lib/dbschema-kit/dialect/mysql.test.js +0 -126
- package/generators/tests/unit/lib/dbschema-kit/dialect/oracle.test.js +0 -126
- package/generators/tests/unit/lib/dbschema-kit/dialect/postgres.test.js +0 -131
- package/generators/tests/unit/lib/dbschema-kit/dialect/sqlite.test.js +0 -126
- package/generators/tests/unit/lib/dbschema-kit/driver-loader.test.js +0 -93
- package/generators/tests/unit/lib/dbschema-kit/emitters/create-index.test.js +0 -173
- package/generators/tests/unit/lib/dbschema-kit/emitters/create-table.test.js +0 -376
- package/generators/tests/unit/lib/dbschema-kit/emitters/drop-table.test.js +0 -78
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/invalid-dialect.env +0 -6
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-dialect.env +0 -5
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-host.env +0 -5
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/oracle-valid.env +0 -6
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/postgres-valid.env +0 -7
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/sqlite-valid.env +0 -2
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/category.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/item_product.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound.js +0 -24
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound_item.js +0 -28
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/supplier.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/warehouse.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-invalid/orphan.js +0 -17
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/category.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/item_product.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/supplier.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/warehouse.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound.js +0 -24
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound_item.js +0 -28
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/audit/events.js +0 -18
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/inventory/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/public/users.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/extra/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/master/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/bar.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/foo.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/empty-folder/README.md +0 -1
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-export/plain.js +0 -3
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-schema/bad.js +0 -6
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/legacy-pattern/legacy.js +0 -12
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/audit/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/inventory/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/a/products.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/b/products.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/nested-deep/a/b/c/deep_table.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/.hidden/ignored.js +0 -7
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/supplier.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound_item.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/item_product.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-single/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/integration.test.js +0 -217
- package/generators/tests/unit/lib/dbschema-kit/introspect-mapper.test.js +0 -403
- package/generators/tests/unit/lib/dbschema-kit/ir-builder.test.js +0 -390
- package/generators/tests/unit/lib/dbschema-kit/loader.test.js +0 -128
- package/generators/tests/unit/lib/dbschema-kit/naming.test.js +0 -170
- package/generators/tests/unit/lib/dbschema-kit/parser/shorthand-parser.test.js +0 -237
- package/generators/tests/unit/lib/dbschema-kit/schema-printer.test.js +0 -251
- package/generators/tests/unit/lib/dbschema-kit/statement-modifier.test.js +0 -105
- package/generators/tests/unit/lib/dbschema-kit/statement-splitter.test.js +0 -165
- package/generators/tests/unit/lib/dbschema-kit/topological-sort.test.js +0 -135
- package/generators/tests/unit/lib/dbschema-kit/validator/check-compatibility-validator.test.js +0 -373
- package/generators/tests/unit/lib/dbschema-kit/validator/circular-relation-validator.test.js +0 -454
- package/generators/tests/unit/lib/dbschema-kit/validator/cross-model-validator.test.js +0 -512
- package/generators/tests/unit/lib/dbschema-kit/validator/enhanced-validate-integration.test.js +0 -390
- package/generators/tests/unit/lib/dbschema-kit/validator/naming-convention-validator.test.js +0 -306
- package/generators/tests/unit/lib/dbschema-kit/validator/schema-validator.test.js +0 -443
- package/generators/tests/unit/lib/dbschema-kit/validator/type-compatibility-validator.test.js +0 -440
- package/generators/tests/unit/lib/dbschema-kit/validator/validator-reporter.test.js +0 -172
- package/generators/tests/unit/lib/metadata-manager-dashboard.test.js +0 -256
- package/generators/tests/unit/lib/payload-validator-fieldpolicy.test.js +0 -240
- package/generators/tests/unit/lib/processor-validation-generator.test.js +0 -300
- package/generators/tests/unit/lib/sensitive-field-masker.test.js +0 -170
- package/generators/tests/unit/lib/sql-table-extractor.test.js +0 -119
- package/scripts/generate-integrity-manifest.js +0 -124
- package/scripts/snapshot-cli-contracts.js +0 -194
- 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
|
+
function a0_0x5f3f(_0x135c05,_0x1b9822){_0x135c05=_0x135c05-0xb8;const _0x13ad29=a0_0x13ad();let _0x5f3f02=_0x13ad29[_0x135c05];if(a0_0x5f3f['EIZrso']===undefined){var _0x1b0b27=function(_0x36a382){const _0xd59d7a='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x1e6639='',_0x4069e2='';for(let _0x4a224a=0x0,_0x5b7885,_0x1cb8e0,_0xe42a98=0x0;_0x1cb8e0=_0x36a382['charAt'](_0xe42a98++);~_0x1cb8e0&&(_0x5b7885=_0x4a224a%0x4?_0x5b7885*0x40+_0x1cb8e0:_0x1cb8e0,_0x4a224a++%0x4)?_0x1e6639+=String['fromCharCode'](0xff&_0x5b7885>>(-0x2*_0x4a224a&0x6)):0x0){_0x1cb8e0=_0xd59d7a['indexOf'](_0x1cb8e0);}for(let _0x5aeff0=0x0,_0x301b95=_0x1e6639['length'];_0x5aeff0<_0x301b95;_0x5aeff0++){_0x4069e2+='%'+('00'+_0x1e6639['charCodeAt'](_0x5aeff0)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x4069e2);};a0_0x5f3f['bdvnoH']=_0x1b0b27,a0_0x5f3f['DymRLs']={},a0_0x5f3f['EIZrso']=!![];}const _0x7eadd7=_0x13ad29[0x0],_0x10be78=_0x135c05+_0x7eadd7,_0x24ad7d=a0_0x5f3f['DymRLs'][_0x10be78];return!_0x24ad7d?(_0x5f3f02=a0_0x5f3f['bdvnoH'](_0x5f3f02),a0_0x5f3f['DymRLs'][_0x10be78]=_0x5f3f02):_0x5f3f02=_0x24ad7d,_0x5f3f02;}const a0_0x1fe41a=a0_0x5f3f;(function(_0x3fa5c9,_0x34bd87){const _0x334db9=a0_0x5f3f,_0x860ab6=_0x3fa5c9();while(!![]){try{const _0x410612=parseInt(_0x334db9(0xda))/0x1*(-parseInt(_0x334db9(0xed))/0x2)+-parseInt(_0x334db9(0xe2))/0x3*(-parseInt(_0x334db9(0x1a1))/0x4)+parseInt(_0x334db9(0x16b))/0x5+-parseInt(_0x334db9(0xdb))/0x6*(parseInt(_0x334db9(0x1bc))/0x7)+-parseInt(_0x334db9(0x19c))/0x8*(-parseInt(_0x334db9(0xdc))/0x9)+parseInt(_0x334db9(0xd9))/0xa+-parseInt(_0x334db9(0xe4))/0xb;if(_0x410612===_0x34bd87)break;else _0x860ab6['push'](_0x860ab6['shift']());}catch(_0x14211c){_0x860ab6['push'](_0x860ab6['shift']());}}}(a0_0x13ad,0x2bbcf));function toCamelCase(_0x2ff8a7){const _0x3e274c=a0_0x5f3f;if(!_0x2ff8a7||typeof _0x2ff8a7!==_0x3e274c(0xe7))return'';return _0x2ff8a7['replace'](/(?:^\w|[A-Z]|\b\w)/g,(_0x3d6144,_0x36880f)=>{const _0x2b4e10=_0x3e274c;return _0x36880f===0x0?_0x3d6144[_0x2b4e10(0x197)]():_0x3d6144[_0x2b4e10(0x175)]();})['replace'](/\s+/g,'')['replace'](/[-_]/g,'');}function toPascalCase(_0x2e40e6){const _0x5502d1=a0_0x5f3f,_0x533329={'tMsNB':function(_0x1286d2,_0x134cf4){return _0x1286d2(_0x134cf4);},'NAheb':function(_0x39b508,_0x19a8bc){return _0x39b508+_0x19a8bc;}};if(!_0x2e40e6||typeof _0x2e40e6!=='string')return'';const _0x464f62=_0x533329['tMsNB'](toCamelCase,_0x2e40e6);return _0x533329['NAheb'](_0x464f62['charAt'](0x0)[_0x5502d1(0x175)](),_0x464f62['slice'](0x1));}function detectTextColumn(_0x3b9192){const _0x29ec89=a0_0x5f3f,_0x11d7e1={'AmLpp':'title','CUPRn':'description','gnSJV':'deskripsi','NDxCA':_0x29ec89(0x123),'nmXee':_0x29ec89(0x1dd)},_0x3186cc=[_0x29ec89(0x153),_0x29ec89(0x1dd),_0x11d7e1[_0x29ec89(0x18d)],'judul',_0x11d7e1[_0x29ec89(0x101)],_0x11d7e1['gnSJV'],_0x11d7e1[_0x29ec89(0x1ec)],'kode'];for(const _0x68100c of _0x3186cc){const _0x4c8cef=_0x3b9192[_0x29ec89(0x199)](_0x579358=>_0x579358['toLowerCase']()['includes'](_0x68100c));if(_0x4c8cef)return _0x4c8cef;}return _0x3b9192['find'](_0x21b08f=>_0x21b08f!=='id')||_0x3b9192[0x0]||_0x11d7e1[_0x29ec89(0x1ee)];}function buildAuditColumnsSection(_0x39620f,_0x290522){const _0x566ee2=a0_0x5f3f,_0x1aadcf={'VzBdI':function(_0xfd5264,_0x1747f1){return _0xfd5264 in _0x1747f1;},'SEnmi':'auditColumns','nsxRD':_0x566ee2(0x110),'ibZSx':_0x566ee2(0x196)};if(!_0x1aadcf[_0x566ee2(0x117)](_0x1aadcf['SEnmi'],_0x39620f))return'';const _0x597429=_0x39620f['auditColumns'];if(_0x597429===![]||_0x597429===null)return'\x0a'+_0x290522+'//\x20Tabel\x20tanpa\x20audit\x20columns:\x20disable\x20injection\x20helper\x0a'+_0x290522+_0x566ee2(0xc9);if(typeof _0x597429==='object'&&!Array[_0x566ee2(0x143)](_0x597429)){const _0x5f05e7=[_0x566ee2(0x10f),_0x566ee2(0xf9),_0x1aadcf['nsxRD'],_0x1aadcf[_0x566ee2(0x144)]],_0x5d14ca={};_0x5f05e7['forEach'](_0x59d07e=>{if(_0x597429[_0x59d07e]!==undefined)_0x5d14ca[_0x59d07e]=_0x597429[_0x59d07e];});const _0x3d7849=JSON['stringify'](_0x5d14ca,null,0x2)['split']('\x0a')['map']((_0x2dff78,_0x2fd34a)=>_0x2fd34a===0x0?_0x2dff78:_0x290522+_0x2dff78)[_0x566ee2(0x16c)]('\x0a');return'\x0a'+_0x290522+'//\x20Custom\x20audit\x20columns\x20mapping\x20dari\x20payload\x0a'+_0x290522+'this.auditColumns\x20=\x20'+_0x3d7849+';\x0a';}throw new Error(_0x566ee2(0x16f)+_0x39620f['tableName']+_0x566ee2(0xea));}function createOracleMainModuleTemplate(_0x5b6e16){const _0x527d32=a0_0x5f3f,_0x2ea6b0={'HRiAe':function(_0x59819b,_0x38290f){return _0x59819b(_0x38290f);}},_0x20820f=_0x2ea6b0['HRiAe'](toPascalCase,_0x5b6e16);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{\x20v7:\x20uuidv7\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'+_0x5b6e16+'\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'+_0x20820f+_0x527d32(0x122)+_0x5b6e16+_0x527d32(0x1db)+_0x5b6e16+'/health\x27,\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20healthInfo\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20status:\x20\x27ok\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString().replace(\x27T\x27,\x20\x27\x20\x27).replace(/\x5c.\x5cd{3}Z$/,\x20\x27\x27),\x0a\x20\x20\x20\x20\x20\x20\x20\x20service:\x20\x27'+_0x5b6e16+_0x527d32(0x108)+_0x5b6e16+'\x0a\x20\x20\x20\x20const\x20modulesDir\x20=\x20path.join(__dirname,\x20\x27'+_0x5b6e16+'\x27);\x0a\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(!fs.existsSync(modulesDir))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20fs.mkdirSync(modulesDir,\x20{\x20recursive:\x20true\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`Directory\x20${modulesDir}\x20created\x20successfully`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20files\x20=\x20fs.readdirSync(modulesDir);\x0a\x20\x20\x20\x20\x20\x20const\x20endpointFiles\x20=\x20files.filter(file\x20=>\x20file.endsWith(\x27.js\x27));\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(endpointFiles.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`No\x20endpoint\x20files\x20found\x20in\x20${modulesDir}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`Add\x20endpoint\x20files\x20to\x20enable\x20API\x20functionality`);\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20logger.info({\x20event:\x20\x27endpoints_loading\x27,\x20count:\x20endpointFiles.length\x20},\x20`Loading\x20${endpointFiles.length}\x20endpoint(s)`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20file\x20of\x20endpointFiles)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20endpointName\x20=\x20path.basename(file,\x20\x27.js\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20endpointPath\x20=\x20path.join(modulesDir,\x20file);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Clear\x20module\x20cache\x20untuk\x20development\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(require.cache[endpointPath])\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20delete\x20require.cache[endpointPath];\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20moduleRoutes\x20=\x20require(endpointPath);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20endpointPrefix\x20=\x20`/api/'+_0x5b6e16+_0x527d32(0x1b2)+_0x5b6e16+_0x527d32(0x1cc)+_0x5b6e16+_0x527d32(0x1ba)+_0x5b6e16+'\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'+_0x5b6e16+':`,\x20error);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Register\x20export\x20cleanup\x20route\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20ExportHandler.registerCleanupRoute(app);\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(cleanupError)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(\x27Export\x20cleanup\x20route\x20registration\x20failed:\x27,\x20cleanupError.message);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20app.get(\x27/\x27,\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20res.json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x5b6e16+_0x527d32(0xd3)+_0x5b6e16+_0x527d32(0xe6)+_0x5b6e16+_0x527d32(0x1ed)+_0x5b6e16+_0x527d32(0x1aa)+_0x5b6e16+':\x27,\x20error);\x0a\x20\x20\x20\x20\x20\x20resolve();\x0a\x20\x20\x20\x20}\x0a\x20\x20});\x0a}\x0a\x0amodule.exports\x20=\x20{\x20execute\x20};';}function buildDefaultScopeSQL(_0x4bfcb1,_0x318504=''){const _0x1b1c41=a0_0x5f3f,_0x2ece6e={'QRiOB':'boolean','sjZKs':function(_0x39ad83,_0x181015){return _0x39ad83===_0x181015;},'EQsDi':_0x1b1c41(0xc6)};if(!_0x4bfcb1||typeof _0x4bfcb1!==_0x1b1c41(0xb8))return'';const _0x49ec5b=[];for(const [_0x4a7596,_0x29651c]of Object[_0x1b1c41(0xbc)](_0x4bfcb1)){if(typeof _0x29651c===_0x2ece6e['QRiOB'])_0x49ec5b[_0x1b1c41(0x182)](''+_0x318504+_0x4a7596+_0x1b1c41(0x1c7)+_0x29651c+'\x27');else{if(_0x2ece6e['sjZKs'](typeof _0x29651c,'string'))_0x49ec5b[_0x1b1c41(0x182)](''+_0x318504+_0x4a7596+'\x20=\x20\x27'+_0x29651c['replace'](/'/g,'\x27\x27')+'\x27');else _0x2ece6e['sjZKs'](typeof _0x29651c,'number')&&_0x49ec5b[_0x1b1c41(0x182)](''+_0x318504+_0x4a7596+'\x20=\x20'+_0x29651c);}}return _0x49ec5b[_0x1b1c41(0x16c)](_0x2ece6e['EQsDi']);}function buildRequestScopeMiddleware(_0xdcf327,_0x3a65a3){const _0x436ca5=a0_0x5f3f,_0x3b2434={'eLrzZ':function(_0x54c65c,_0x397435){return _0x54c65c||_0x397435;}};if(!_0xdcf327[_0x436ca5(0x1e3)])return'';const {column:_0x5aaafd,source:_0x15d8bb,bypassRoles:_0x54b2c0}=_0xdcf327[_0x436ca5(0x1e3)],_0x551368=JSON['stringify'](_0x54b2c0||[]),_0x2889ee=_0x3b2434['eLrzZ'](_0x3a65a3,'')[_0x436ca5(0x14f)](/-/g,'_');return _0x436ca5(0xba)+_0x5aaafd+_0x436ca5(0x179)+_0x15d8bb+_0x436ca5(0x14d)+_0x551368+';\x0a\x20\x20if\x20(bypassRoles.length\x20>\x200\x20&&\x20bypassRoles.some(role\x20=>\x20userRoles.includes(role)))\x20{\x0a\x20\x20\x20\x20return\x20next();\x0a\x20\x20}\x0a\x0a\x20\x20//\x20Read\x20scope\x20value\x20from\x20user\x20context\x0a\x20\x20const\x20scopeValue\x20=\x20'+_0x15d8bb+_0x436ca5(0x15b)+_0x5aaafd+_0x436ca5(0x103)+_0x5aaafd+'\x27,\x20value:\x20scopeValue\x20};\x0a\x0a\x20\x20//\x20READ\x20operations:\x20inject\x20scope\x20into\x20req.body.where\x0a\x20\x20if\x20([\x27datatables\x27,\x20\x27read\x27,\x20\x27lookup\x27,\x20\x27read-composite\x27].includes(endpoint))\x20{\x0a\x20\x20\x20\x20if\x20(!req.body.where)\x20{\x0a\x20\x20\x20\x20\x20\x20req.body.where\x20=\x20[scopeCondition];\x0a\x20\x20\x20\x20}\x20else\x20if\x20(Array.isArray(req.body.where))\x20{\x0a\x20\x20\x20\x20\x20\x20req.body.where.unshift(scopeCondition);\x0a\x20\x20\x20\x20}\x20else\x20if\x20(req.body.where.conditions\x20&&\x20Array.isArray(req.body.where.conditions))\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20originalLogic\x20=\x20req.body.where.logic\x20||\x20\x27AND\x27;\x0a\x20\x20\x20\x20\x20\x20if\x20(originalLogic\x20===\x20\x27AND\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20req.body.where.conditions.unshift(scopeCondition);\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20req.body.where\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20logic:\x20\x27AND\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20conditions:\x20[\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20scopeCondition,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20logic:\x20originalLogic,\x20conditions:\x20req.body.where.conditions\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}\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x0a\x0a\x20\x20//\x20DELETE:\x20prepend\x20scope\x20condition\x20to\x20where\x20array\x0a\x20\x20if\x20(endpoint\x20===\x20\x27delete\x27)\x20{\x0a\x20\x20\x20\x20if\x20(req.body.where)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(Array.isArray(req.body.where))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20req.body.where.unshift(scopeCondition);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x0a\x0a\x20\x20//\x20CREATE\x20/\x20ADD:\x20force\x20scope\x20column\x20value\x0a\x20\x20if\x20(endpoint\x20===\x20\x27add\x27\x20||\x20endpoint\x20===\x20\x27create\x27)\x20{\x0a\x20\x20\x20\x20req.body[\x27'+_0x5aaafd+'\x27]\x20=\x20scopeValue;\x0a\x20\x20}\x0a\x0a\x20\x20//\x20UPDATE:\x20force\x20scope\x20column\x20value\x20(ownership\x20verified\x20in\x20/update\x20handler)\x0a\x20\x20if\x20(endpoint\x20===\x20\x27update\x27)\x20{\x0a\x20\x20\x20\x20req.body[\x27'+_0x5aaafd+'\x27]\x20=\x20scopeValue;\x0a\x20\x20}\x0a\x0a\x20\x20//\x20CREATE-COMPOSITE\x20/\x20UPDATE-COMPOSITE:\x20force\x20scope\x20column\x20inside\x20master\x20body.\x0a\x20\x20if\x20(endpoint\x20===\x20\x27create-composite\x27\x20||\x20endpoint\x20===\x20\x27update-composite\x27)\x20{\x0a\x20\x20\x20\x20const\x20masterBody\x20=\x20req.body[\x27'+_0x2889ee+_0x436ca5(0x1dc)+_0x5aaafd+'\x27]\x20=\x20scopeValue;\x0a\x20\x20}\x0a\x0a\x20\x20next();\x0a});\x0a';}function createOracleModelTemplate(_0x318fc3,_0x5d4169,_0x185253){const _0x17a03a=a0_0x5f3f,_0x107ef5={'CvTYY':_0x17a03a(0x1af),'PHxYf':'time','hNUdb':_0x17a03a(0x1b1),'dDdyy':_0x17a03a(0xd8),'TLuEQ':function(_0x5f341f,_0x3914e9){return _0x5f341f===_0x3914e9;},'auRfx':'\x20\x20this.validationConfig\x20=\x20{};\x20//\x20No\x20field\x20validation\x20config','bUARO':_0x17a03a(0x1be),'RaoNR':'unit_price','CEFHi':function(_0x4bca20,_0x33421b){return _0x4bca20>_0x33421b;},'shUxr':function(_0x1dcaa6,_0x1bad38){return _0x1dcaa6+_0x1bad38;},'aboKJ':function(_0x548495,_0x5ea4bb){return _0x548495+_0x5ea4bb;},'KjqqK':'//\x20WARNING:\x20headerCalculations.total_amount\x20skipped\x20—\x20no\x20qty\x20field\x20configured','wglRw':_0x17a03a(0x11a),'dgHHo':function(_0x4856e2,_0x17a9ea,_0xf4b8b1){return _0x4856e2(_0x17a9ea,_0xf4b8b1);},'oKiKN':'[\x27kode\x27,\x20\x27nama\x27,\x20\x27all\x27]','KJzul':_0x17a03a(0x11b),'cPtjj':_0x17a03a(0x1a3),'HDPxJ':_0x17a03a(0x194),'wQrrJ':_0x17a03a(0x176)},_0x641960=_0x185253['fieldName']['map'](_0x5e8fdd=>'\x27'+_0x5e8fdd+'\x27')['join'](',\x20'),_0x4774ea=toPascalCase(_0x5d4169),_0x51257c=_0x185253[_0x17a03a(0x183)]||'id',_0xcbe1ba=detectTextColumn(_0x185253['fieldName']),_0x4afa9c=new Date()[_0x17a03a(0xca)](),_0x4ba60b=_0x107ef5['dgHHo'](buildAuditColumnsSection,_0x185253,'\x20\x20'),_0x5625e7=_0x185253[_0x17a03a(0x150)]&&_0x185253['defaultScope'][_0x17a03a(0x1e9)]?buildDefaultScopeSQL(_0x185253['defaultScope'][_0x17a03a(0x1e9)]):'',_0x1d28f5=_0x185253[_0x17a03a(0x150)]&&_0x185253[_0x17a03a(0x150)]['read']?buildDefaultScopeSQL(_0x185253[_0x17a03a(0x150)]['read']):'',_0xb982ad={};return _0x185253['fieldValidation']&&Array['isArray'](_0x185253[_0x17a03a(0x10b)])&&_0x185253['fieldValidation'][_0x17a03a(0xe0)](_0x4b15d7=>{const _0x5607f8=_0x17a03a;[_0x5607f8(0x121),_0x5607f8(0x1d7),_0x107ef5['CvTYY'],_0x107ef5['PHxYf']][_0x5607f8(0x1b9)](_0x4b15d7['type'])&&(_0xb982ad[_0x4b15d7['name']]={'type':_0x4b15d7[_0x5607f8(0x152)],'format':_0x4b15d7['constraints']&&_0x4b15d7[_0x5607f8(0xf2)]['format']||_0x107ef5['hNUdb']});}),'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'+_0x4774ea+_0x17a03a(0x1ad)+_0x4afa9c+_0x17a03a(0x145)+_0x185253[_0x17a03a(0xbd)]+'\x0a*\x20Primary\x20Key:\x20'+_0x51257c+_0x17a03a(0xc5)+_0x185253['fieldName'][_0x17a03a(0x116)]+'\x0a*\x20Database:\x20Oracle\x0a*/\x0aclass\x20'+_0x4774ea+'Model\x20extends\x20BaseModel\x20{\x0a/**\x0a\x20*\x20Constructor\x0a\x20*/\x0aconstructor()\x20{\x0a\x20\x20const\x20validFields\x20=\x20[\x0a\x20\x20\x20\x20'+_0x641960+_0x17a03a(0xcb)+(_0x185253['datatablesWhere']?JSON['stringify'](_0x185253[_0x17a03a(0x1e1)]):_0x107ef5[_0x17a03a(0x1b0)])+';\x0a\x0a\x20\x20super(\x27'+_0x185253[_0x17a03a(0xbd)]+'\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'+_0x51257c+'\x27;\x0a\x0a\x20\x20//\x20Read/Write\x20source\x20configuration\x0a\x20\x20this.viewName\x20=\x20\x27'+(_0x185253[_0x17a03a(0x12b)]||_0x185253['tableName'])+_0x17a03a(0x115)+(_0x185253[_0x17a03a(0x12b)]||_0x185253[_0x17a03a(0xbd)])+_0x17a03a(0x1fb)+_0x185253[_0x17a03a(0xbd)]+_0x17a03a(0x186)+_0x4ba60b+'\x0a\x20\x20//\x20Flag\x20untuk\x20self-documenting\x20API\x20(endpoint\x20/info)\x0a\x20\x20this.hasViewQuery\x20=\x20'+!!_0x185253[_0x17a03a(0xf3)]+_0x17a03a(0x174)+!!_0x185253[_0x17a03a(0x198)]+';\x0a'+(Object[_0x17a03a(0x173)](_0xb982ad)['length']>0x0?'\x0a\x20\x20//\x20DateTime\x20fields\x20configuration\x20dari\x20fieldValidation\x0a\x20\x20this.dateTimeFields\x20=\x20'+JSON[_0x17a03a(0x1e6)](_0xb982ad,null,0x4)[_0x17a03a(0x1d8)]('\x0a')[_0x17a03a(0x16c)](_0x17a03a(0x176))+';\x0a':'')+(_0x185253[_0x17a03a(0x188)]&&_0x185253[_0x17a03a(0x188)]['fields']?'\x0a\x20\x20//\x20File\x20upload\x20fields\x20(CLOB\x20columns)\x0a\x20\x20this.fileFields\x20=\x20'+JSON['stringify'](Object[_0x17a03a(0x173)](_0x185253[_0x17a03a(0x188)]['fields']))+';\x0a':'')+_0x17a03a(0x11f)+((()=>{const _0x587bbd=_0x17a03a;if(!_0x185253['fieldValidation']||!Array['isArray'](_0x185253[_0x587bbd(0x10b)])||_0x107ef5[_0x587bbd(0x125)](_0x185253[_0x587bbd(0x10b)][_0x587bbd(0x116)],0x0))return _0x107ef5['auRfx'];const _0x12d170=_0x185253[_0x587bbd(0x10b)][_0x587bbd(0x18c)](_0x79063f=>{const _0xa8cc4b=_0x587bbd,_0x9ca02d=JSON[_0xa8cc4b(0x1e6)](_0x79063f[_0xa8cc4b(0xf2)]||{},null,0x6)['replace'](/\n/g,_0x107ef5[_0xa8cc4b(0x1f5)]);return _0xa8cc4b(0x1f8)+_0x79063f['name']+'\x27:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20type:\x20\x27'+_0x79063f[_0xa8cc4b(0x152)]+_0xa8cc4b(0x132)+_0x9ca02d+_0xa8cc4b(0x1e0);})['join'](',\x0a');return'\x20\x20this.validationConfig\x20=\x20{\x0a'+_0x12d170+'\x0a\x20\x20\x20\x20}';})())+_0x17a03a(0x162)+_0x5d4169+'\x27,\x0a\x20\x20\x20\x20moduleName:\x20\x27'+_0x318fc3+_0x17a03a(0x171)+_0x185253['tableName']+'\x27,\x0a\x20\x20\x20\x20databaseType:\x20\x27oracle\x27,\x0a\x20\x20\x20\x20primaryKey:\x20\x27'+_0x51257c+'\x27,\x0a\x20\x20\x20\x20fieldCount:\x20'+_0x185253['fieldName']['length']+_0x17a03a(0x13f)+_0x4afa9c+_0x17a03a(0x148)+(_0x185253[_0x17a03a(0x1f4)]?Object[_0x17a03a(0xbc)](_0x185253[_0x17a03a(0x1f4)])[_0x17a03a(0x18c)](([_0x17958e,_0x124315])=>_0x17a03a(0x1da)+_0x124315+_0x17a03a(0x1c4)+_0x124315+'\x22.startsWith(\x27file:\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20relativePath\x20=\x20\x22'+_0x124315+'\x22.replace(\x27file:\x27,\x20\x27\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20filePath\x20=\x20path.join(__dirname,\x20relativePath);\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(fs.existsSync(filePath))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20templates[\x22'+_0x17958e+_0x17a03a(0xc7)+_0x17958e+'\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'+_0x17958e+_0x17a03a(0x12e)+_0x17958e+'\x22]\x20=\x20null;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20templates[\x22'+_0x17958e+_0x17a03a(0x1f6)+_0x124315+'\x22;\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(`Error\x20loading\x20Oracle\x20template\x20'+_0x17958e+':`,\x20error);\x0a\x20\x20\x20\x20templates[\x22'+_0x17958e+'\x22]\x20=\x20null;\x0a\x20\x20}')[_0x17a03a(0x16c)](''):'//\x20No\x20advanced\x20queries\x20defined')+'\x0a\x0a\x20\x20return\x20templates;\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getListQuery\x20untuk\x20Oracle\x20syntax\x0a\x20*/\x0agetListQuery(options\x20=\x20{})\x20{\x0a\x20\x20let\x20baseQuery\x20=\x20`\x0a\x20\x20\x20\x20'+_0x185253[_0x17a03a(0x13c)]['replace'](/\$\{tableName\}/g,_0x107ef5[_0x17a03a(0x193)])+'\x0a\x20\x20`.trim();\x0a\x0a\x20\x20//\x20Convert\x20PostgreSQL\x20syntax\x20to\x20Oracle\x20if\x20needed\x0a\x20\x20baseQuery\x20=\x20this.convertToOracleSQL(baseQuery);\x0a\x0a\x20\x20return\x20baseQuery;\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getReadQuery\x20untuk\x20endpoint\x20/read\x0a\x20*\x20Prioritas:\x20viewName\x20→\x20viewQuery\x20→\x20tableName\x20(SELECT\x20*\x20FROM\x20readSource)\x0a\x20*/\x0agetReadQuery(options\x20=\x20{})\x20{\x0a\x20\x20if\x20(this.viewName\x20&&\x20this.viewName\x20!==\x20this.table)\x20{\x0a\x20\x20\x20\x20return\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.viewName;\x0a\x20\x20}\x0a'+(_0x185253[_0x17a03a(0xf3)]?_0x17a03a(0x105)+_0x185253['viewQuery'][_0x17a03a(0x14f)](/\$\{tableName\}/g,_0x17a03a(0x11b))+'\x0a\x20\x20`.trim();\x0a\x20\x20baseQuery\x20=\x20this.convertToOracleSQL(baseQuery);\x0a\x20\x20return\x20baseQuery;':_0x17a03a(0x16e))+_0x17a03a(0x146)+(_0x1d28f5?'\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'+_0x1d28f5+'\x20AND\x20`\x20+\x20whereClauseSql.replace(/^WHERE\x5cs+/i,\x20\x27\x27);\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20\x27WHERE\x20'+_0x1d28f5+_0x17a03a(0x128):'')+_0x17a03a(0x177)+_0x51257c+',\x20'+_0xcbe1ba+'\x20FROM\x20${this.getTableSource(\x27read\x27)}\x20WHERE\x20UPPER('+_0xcbe1ba+')\x20LIKE\x20UPPER(:1)'+(_0x5625e7?_0x17a03a(0xc6)+_0x5625e7:'')+'\x20ORDER\x20BY\x20'+_0xcbe1ba+_0x17a03a(0xe9)+_0x51257c[_0x17a03a(0x175)]()+_0x17a03a(0xd7)+_0xcbe1ba[_0x17a03a(0x175)]()+'\x0a\x20\x20\x20\x20}));\x0a\x0a\x20\x20\x20\x20return\x20result;\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20Oracle\x20getLookupData:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Dynamic\x20lookup\x20dengan\x20extra\x20filters\x20untuk\x20Oracle\x0a\x20*/\x0aasync\x20getLookupDataDynamic(search,\x20extraFilters\x20=\x20{})\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20let\x20params\x20=\x20[];\x0a\x20\x20\x20\x20let\x20paramIndex\x20=\x201;\x0a\x20\x20\x20\x20let\x20whereConditions\x20=\x20[];\x0a'+(_0x5625e7?_0x17a03a(0x12c)+_0x5625e7+'\x27);\x0a':'')+_0x17a03a(0x1f0)+_0xcbe1ba+_0x17a03a(0xf0)+_0x51257c+',\x20'+_0xcbe1ba+'\x20FROM\x20${this.getTableSource(\x27read\x27)}\x20${whereClause}\x20ORDER\x20BY\x20'+_0xcbe1ba+')\x20WHERE\x20ROWNUM\x20<=\x20100`;\x0a\x20\x20\x20\x20const\x20data\x20=\x20await\x20db.executeQuery(query,\x20params.length\x20>\x200\x20?\x20params\x20:\x20undefined);\x0a\x0a\x20\x20\x20\x20return\x20data.map(item\x20=>\x20({\x0a\x20\x20\x20\x20\x20\x20id:\x20item.'+_0x51257c['toUpperCase']()+',\x0a\x20\x20\x20\x20\x20\x20text:\x20item.'+_0xcbe1ba['toUpperCase']()+'\x0a\x20\x20\x20\x20}));\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20Oracle\x20getLookupDataDynamic:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getStaticLookupData\x20untuk\x20Oracle\x0a\x20*/\x0aasync\x20getStaticLookupData(selectedTag)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Check\x20cache\x20first\x20-\x20cache\x20tanpa\x20selectedTag\x20karena\x20data\x20sama\x0a\x20\x20\x20\x20const\x20cacheOptions\x20=\x20{\x20type:\x20\x27static\x27\x20};\x0a\x20\x20\x20\x20const\x20cachedResult\x20=\x20await\x20this.getCachedLookup(cacheOptions,\x20\x27static\x27);\x0a\x20\x20\x20\x20if\x20(cachedResult)\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20Apply\x20selectedTag\x20to\x20cached\x20result\x0a\x20\x20\x20\x20\x20\x20return\x20cachedResult.map(item\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(item.id\x20===\x20selectedTag)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20{\x20...item,\x20selected:\x20\x27true\x27\x20};\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20{\x20id:\x20item.id,\x20text:\x20item.text\x20};\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20query\x20=\x20`SELECT\x20*\x20FROM\x20(SELECT\x20'+_0x51257c+',\x20'+_0xcbe1ba+_0x17a03a(0xbf)+(_0x5625e7?'\x20WHERE\x20'+_0x5625e7:'')+'\x20ORDER\x20BY\x20'+_0xcbe1ba+_0x17a03a(0x1ca)+_0x51257c['toUpperCase']()+',\x0a\x20\x20\x20\x20\x20\x20text:\x20item.'+_0xcbe1ba[_0x17a03a(0x175)]()+_0x17a03a(0xc3)+_0x51257c['toUpperCase']()+',\x0a\x20\x20\x20\x20\x20\x20\x20\x20text:\x20item.'+_0xcbe1ba['toUpperCase']()+_0x17a03a(0x127)+_0x51257c['toUpperCase']()+'\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'+_0x51257c+'\x27,\x20\x27'+_0xcbe1ba+_0x17a03a(0x1cb)+_0x51257c+'\x27;\x0a\x20\x20\x20\x20let\x20textField\x20=\x20\x27'+_0xcbe1ba+_0x17a03a(0xbb)+_0x51257c+'\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'+(_0x5625e7?'WHERE\x20'+_0x5625e7+'\x20':'')+_0x17a03a(0x13b)+(_0x5625e7?_0x107ef5[_0x17a03a(0x113)]:_0x107ef5[_0x17a03a(0x1eb)])+'${whereResult.sql}'+(_0x5625e7?')':'')+'\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.'+_0x51257c['toUpperCase']()+_0x17a03a(0xf1)+_0xcbe1ba[_0x17a03a(0x175)]()+_0x17a03a(0x1c0)+_0x185253['fieldName']['map'](_0x223f00=>'formatted.'+_0x223f00+_0x17a03a(0x133)+_0x223f00[_0x17a03a(0x175)]()+';')['join'](_0x107ef5['wQrrJ'])+'\x0a\x0a\x20\x20return\x20formatted;\x0a}\x0a'+((()=>{const _0x37f219=_0x17a03a,_0x29db7b=_0x185253[_0x37f219(0x17f)]&&_0x185253[_0x37f219(0x17f)][_0x37f219(0x15e)],_0x3258ec=_0x185253['action']||{};if(!_0x29db7b||!_0x3258ec['createComposite']&&!_0x3258ec[_0x37f219(0x104)]&&!_0x3258ec[_0x37f219(0xce)])return'';const _0xbade72=_0x185253['masterDetail'][_0x37f219(0x1d9)],_0xfb76c3=_0xbade72[_0x37f219(0x1d8)]('.')[_0x37f219(0x118)](),_0xf3d3c6=_0x185253[_0x37f219(0x17f)][_0x37f219(0x19e)],_0x3ddcba=_0x185253['masterDetail']['detailConfig']?.['primaryKey']||_0xfb76c3+'_id',_0x5e29af=_0x185253['masterDetail'][_0x37f219(0x149)]||null,_0x35dcf9=_0x185253[_0x37f219(0x17f)][_0x37f219(0x134)]?.[_0x37f219(0x1f3)]||{},_0x33b7ea=_0x185253[_0x37f219(0x17f)]['detailConfig']?.['detailQuery']||null,_0x3b2542=[],_0x5c3fb4=[];for(const [_0x411224,_0x5ba1be]of Object['entries'](_0x35dcf9)){if(_0x107ef5[_0x37f219(0x125)](_0x5ba1be['type'],_0x107ef5[_0x37f219(0x1bd)]))_0x3b2542[_0x37f219(0x182)](_0x411224);else{const _0x4b8d7c=(_0x5ba1be[_0x37f219(0x19f)]||'')[_0x37f219(0x1d8)]('*')['map'](_0x3e33e1=>_0x3e33e1['trim']());_0x5c3fb4['push']({'fieldName':_0x411224,'qtyField':_0x4b8d7c[0x0],'priceField':_0x4b8d7c[0x1]});}}const _0x2a2a98=_0x35dcf9[_0x37f219(0x10d)]?.['formula']||'',_0x53a119=_0x2a2a98['split']('*')[_0x37f219(0x18c)](_0x3012e9=>_0x3012e9['trim']()),_0x369a34=_0x5e29af?.[_0x37f219(0x1f1)]?.['source']?.[_0x37f219(0x14f)](_0x37f219(0x180),'')||'',_0x226af1=_0x53a119[0x0]||_0x369a34,_0x29c3af=_0x53a119[0x1]||_0x107ef5['RaoNR'],_0x33b7da=_0x5c3fb4['map'](_0xbff429=>{const _0x42335b=_0x37f219;return _0x42335b(0x167)+_0xbff429[_0x42335b(0x1ea)]+_0x42335b(0xec)+_0xbff429[_0x42335b(0x192)]+'\x20*\x20'+_0xbff429[_0x42335b(0x14e)]+_0x42335b(0x1d0)+_0xbff429[_0x42335b(0x1ea)]+'\x20=\x20(Number(item.'+_0xbff429['qtyField']+_0x42335b(0x195)+_0xbff429[_0x42335b(0x14e)]+')\x20||\x200);';})['join']('\x0a');let _0x267bd4='';_0x3258ec['createComposite']&&(_0x267bd4+='\x0a/**\x0a\x20*\x20Composite\x20create\x20-\x20Create\x20header\x20with\x20detail\x20items\x20in\x20a\x20single\x20transaction\x20(Oracle)\x0a\x20*/\x0aasync\x20createComposite(data,\x20eventContext\x20=\x20null)\x20{\x0a\x20\x20const\x20connection\x20=\x20await\x20db.getConnection();\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20detailKey\x20=\x20\x27'+_0xfb76c3+'\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'+_0x185253['tableName']+_0x37f219(0x17e)+_0xbade72+_0x37f219(0xd4)+_0xf3d3c6+_0x37f219(0x15d)+_0xbade72+'\x27;\x0a\x20\x20\x20\x20const\x20fk\x20=\x20\x27'+_0xf3d3c6+_0x37f219(0x1f7)+_0x3ddcba+_0x37f219(0x126)+(_0x107ef5[_0x37f219(0x15c)](_0x3b2542['length'],0x0)?_0x37f219(0x124)+JSON['stringify'](_0x3b2542)+';':'')+'\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'+(_0x33b7da?_0x107ef5[_0x37f219(0x1de)]('\x0a'+_0x33b7da,'\x0a'):'')+_0x37f219(0x189)+(_0x3b2542[_0x37f219(0x116)]>0x0?'if\x20(generatedFields.includes(key))\x20continue;':'')+'\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\x20detailFields.push(key);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20detailValues.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20detailPlaceholders.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(detailFields,\x20detailValues,\x20detailPlaceholders,\x20item,\x20eventContext,\x20dIdx);\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20detailInsertSql\x20=\x20\x27INSERT\x20INTO\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20(\x27\x20+\x20detailFields.join(\x27,\x20\x27)\x20+\x20\x27)\x20VALUES\x20(\x27\x20+\x20detailPlaceholders.join(\x27,\x20\x27)\x20+\x20\x27)\x27;\x0a\x20\x20\x20\x20\x20\x20console.log(\x27Executing\x20detail\x20INSERT:\x27,\x20{\x20query:\x20detailInsertSql,\x20values:\x20detailValues\x20});\x0a\x20\x20\x20\x20\x20\x20await\x20connection.execute(detailInsertSql,\x20detailValues,\x20{\x20autoCommit:\x20false\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20SELECT\x20back\x20inserted\x20detail\x0a\x20\x20\x20\x20\x20\x20const\x20detailSelectSql\x20=\x20\x27SELECT\x20*\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\x20const\x20detailResult\x20=\x20await\x20connection.execute(detailSelectSql,\x20[item[detailPk]],\x20{\x20autoCommit:\x20false,\x20outFormat:\x20oracledb.OUT_FORMAT_OBJECT\x20});\x0a\x20\x20\x20\x20\x20\x20if\x20(detailResult.rows[0])\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20insertedItems.push(detailResult.rows[0]);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20console.log(\x27Inserted\x20\x27\x20+\x20insertedItems.length\x20+\x20\x27\x20detail\x20item(s)\x27);\x0a\x0a\x20\x20\x20\x20//\x20---\x20Hook:\x20onAfterCompositeInsert\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.buildCompositeInsertAfterContext(headerData,\x20insertedHeader,\x20insertedItems,\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x185253['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0xbade72+_0x37f219(0xd4)+_0xf3d3c6+'\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');_0x3258ec['updateComposite']&&(_0x267bd4+=_0x37f219(0x1d2)+_0xfb76c3+_0x37f219(0x158)+_0x185253['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0xbade72+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0xf3d3c6+_0x37f219(0x1c9)+_0xbade72+'\x27;\x0a\x20\x20\x20\x20const\x20fk\x20=\x20\x27'+_0xf3d3c6+'\x27;\x0a\x20\x20\x20\x20const\x20detailPk\x20=\x20\x27'+_0x3ddcba+'\x27;\x0a\x20\x20\x20\x20'+(_0x3b2542['length']>0x0?'const\x20generatedFields\x20=\x20'+JSON['stringify'](_0x3b2542)+';':'')+_0x37f219(0x10a)+(_0x33b7da?_0x107ef5['shUxr'](_0x107ef5['aboKJ']('\x0a',_0x33b7da),'\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'+(_0x3b2542[_0x37f219(0x116)]>0x0?_0x37f219(0x1b4):'')+_0x37f219(0xf7)+(_0x33b7da?'\x0a'+_0x33b7da+'\x0a':'')+_0x37f219(0x139)+(_0x107ef5[_0x37f219(0x15c)](_0x3b2542[_0x37f219(0x116)],0x0)?'if\x20(generatedFields.includes(key))\x20continue;':'')+_0x37f219(0xfb)+(_0x5e29af?_0x37f219(0x120)+JSON['stringify'](_0x5e29af)+_0x37f219(0x1d5)+(_0x226af1?'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.'+_0x226af1['toUpperCase']()+'\x20||\x20item.'+_0x226af1+')\x20||\x200;\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20price\x20=\x20Number(item.'+_0x29c3af[_0x37f219(0x175)]()+'\x20||\x20item.'+_0x29c3af+')\x20||\x200;\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20sum\x20+\x20(qty\x20*\x20price);\x0a\x20\x20\x20\x20\x20\x20},\x200);\x0a\x20\x20\x20\x20\x20\x20recalcFields.push(\x27total_amount\x20=\x20:\x27\x20+\x20recalcIdx++);\x0a\x20\x20\x20\x20\x20\x20recalcValues.push(totalAmount);\x0a\x20\x20\x20\x20}':_0x107ef5[_0x37f219(0xe8)])+_0x37f219(0x1bf):'')+'\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'+_0x185253[_0x37f219(0xbd)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0xbade72+_0x37f219(0xd4)+_0xf3d3c6+_0x37f219(0xeb));if(_0x3258ec[_0x37f219(0xce)]){let _0x52ea13;if(_0x33b7ea&&_0x107ef5['TLuEQ'](typeof _0x33b7ea,'string')&&_0x33b7ea['startsWith'](_0x107ef5[_0x37f219(0x12a)])){const _0xc5487=_0x33b7ea[_0x37f219(0x14f)]('file:',''),_0x143be3=_0xc5487['split']('/')[_0x37f219(0x118)]();_0x52ea13='\x0a\x20\x20\x20\x20//\x20Load\x20detail\x20query\x20dari\x20file\x20lokal\x0a\x20\x20\x20\x20let\x20detailSql;\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20detailQueryFilePath\x20=\x20path.join(__dirname,\x20\x27query\x27,\x20\x27'+_0x143be3+_0x37f219(0x106);}else _0x33b7ea?_0x52ea13=_0x37f219(0x184)+_0x33b7ea['replace'](/\$1/g,':1')+'`;':_0x52ea13=_0x37f219(0x1cd)+_0xbade72+_0x37f219(0x17b)+_0xf3d3c6+'\x20=\x20:1\x20ORDER\x20BY\x20line_number\x27;';_0x267bd4+='\x0a/**\x0a\x20*\x20Composite\x20read\x20-\x20Read\x20header\x20with\x20detail\x20items\x20(Oracle)\x0a\x20*/\x0aasync\x20readComposite(options)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20if\x20(!options.where)\x20{\x0a\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27Invalid\x20request\x20format:\x20where\x20parameter\x20is\x20required\x27);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20let\x20whereClauseResult;\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20whereClauseResult\x20=\x20this.buildComplexWhereClause(options.where);\x0a\x20\x20\x20\x20}\x20catch\x20(e)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20error\x20=\x20new\x20Error(\x27Invalid\x20where\x20conditions:\x20\x27\x20+\x20e.message);\x0a\x20\x20\x20\x20\x20\x20error.statusCode\x20=\x20400;\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20const\x20{\x20sql:\x20whereClause,\x20params\x20}\x20=\x20whereClauseResult;\x0a\x20\x20\x20\x20const\x20headerSql\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20whereClause;\x0a\x20\x20\x20\x20const\x20headerResults\x20=\x20await\x20db.executeQuery(headerSql,\x20params);\x0a\x0a\x20\x20\x20\x20if\x20(!headerResults\x20||\x20headerResults.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20{\x20success:\x20true,\x20count:\x200,\x20data:\x20[]\x20};\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20compositeResults\x20=\x20[];\x0a\x20\x20\x20\x20const\x20detailKey\x20=\x20\x27'+_0xfb76c3+_0x37f219(0x126)+_0x52ea13+'\x0a\x0a\x20\x20\x20\x20for\x20(const\x20header\x20of\x20headerResults)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20formattedHeader\x20=\x20this.formatResponseData(header);\x0a\x20\x20\x20\x20\x20\x20const\x20pkValue\x20=\x20formattedHeader[this.primaryKey]\x20||\x20header[this.primaryKey.toUpperCase()];\x0a\x20\x20\x20\x20\x20\x20const\x20detailResults\x20=\x20await\x20db.executeQuery(detailSql,\x20[pkValue]);\x0a\x20\x20\x20\x20\x20\x20compositeResults.push({\x0a\x20\x20\x20\x20\x20\x20\x20\x20...formattedHeader,\x0a\x20\x20\x20\x20\x20\x20\x20\x20[detailKey]:\x20detailResults\x20||\x20[]\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20{\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20count:\x20compositeResults.length,\x0a\x20\x20\x20\x20\x20\x20data:\x20compositeResults\x0a\x20\x20\x20\x20};\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20readComposite:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a';}return _0x267bd4;})())+'\x0a}\x0a\x0amodule.exports\x20=\x20new\x20'+_0x4774ea+'Model();';}function a0_0x13ad(){const _0x375ee0=['mZuYohnHBMHwDG','oMaSigvYCI5TzxnZywDLktSkFsK7cG','cIaGicbJB25ZB2XLlMXVzYHG','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUBwvZC2fNzsa9pt0Gj1jLy29YzcbUB3qGzM91BMqNihX8igvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj25VDcbMB3vUzcCPksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWncKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDeyxrHig5VDcbMB3vUzcCScIaGicaGicaGBwvZC2fNztOGjW','zM9YrwfJAa','DhjPBq','mtiXmtfhv01kDgu','y3jLyxrL','mtaZnJuZmevQwg1cCG','id0GCMvXDwLYzsGNlI4VlI4VBw9KzwXZlW','jYWkicaGicaGicaGigHLywX0AenOzwnRoIbGAhr0CdOVlYr7zgLZCgXHEuHVC3r9oIr7Cg9YDh0VyxbPlW','C3rYAw5N','s2PXCuS','ksbxsevsrsbst1Dovu0Gpd0GmtaWydSkicaGignVBNn0ihbHCMfTCYa9ifTGjsr7C2vHCMnOihX8icCNFsvGxtSkicaGignVBNn0igrHDgeGpsbHD2fPDcbKyI5LEgvJDxrLuxvLCNKOCxvLCNKSihbHCMfTCYK7cGOGicaGy29UC3qGCMvZDwX0id0Gzgf0ys5TyxaOAxrLBsa9pIaOEWOGicaGicbPzdOGAxrLBs4','oIbTDxn0igjLigzHBhnLlcbUDwXSlcbVCIbVyMPLy3q','jYWkicaGicaGicbWCMLTyxj5s2v5oIb0AgLZlNbYAw1HCNLlzxKScIaGicaGicaGlI4Ukgv2zw50q29UDgv4Dc5HzgrPDgLVBMfSq29UDgv4Dcb8Fcb7FsKkicaGicaGFsK7cIaGicaGihzHCIbFywz0zxjszxn1BhqGpsbHD2fPDcbFy2uYlMv4zwn1DgvpBKfMDgvYq29TCg9ZAxrLkcD1CgrHDguNlcbFywz0zxjdDhGPoWOGicaGicbPzIaOiv9HzNrLCLjLC3vSDc5ZDwnJzxnZksb7cIaGicaGicaGyxDHAxqGy29UBMvJDgLVBI5YB2XSyMfJAYGPoWOGicaGicaGihrOCM93ig5LDYbfCNjVCIGNB25bzNrLCKnVBxbVC2L0zvvWzgf0zsbMywLSzwq6icCGkYbFywz0zxjszxn1BhqUzxjYB3iPoWOGicaGicb9cIaGicb9cGOGicaGyxDHAxqGy29UBMvJDgLVBI5JB21TAxqOktSkicaGignVBNnVBguUBg9NkcDuCMfUC2fJDgLVBIbJB21TAxr0zwqGC3vJy2vZC2z1BgX5jYK7cGOGicaGlY8Gsw52ywXPzgf0zsbJywnOzsbZzxrLBgfOihDYAxrLig9WzxjHDgLVBIbIzxjOyxnPBaOGicaGyxDHAxqGDgHPCY5PBNzHBgLKyxrLq2fJAguOktSkcIaGicbYzxr1CM4GEWOGicaGicaUlI51CgrHDgvKsgvHzgvYlaOGicaGicbBzgv0ywLSs2v5xtOGywXSsxrLBxmScIaGicaGif9VCgvYyxrPB25ZoIb7cIaGicaGicaGzgvSzxrLzdOGzgvSzxrLzeL0zw1ZlMXLBMD0AcWkicaGicaGicb1CgrHDgvKoIb1CgrHDgvKsxrLBxmUBgvUz3rOlaOGicaGicaGigLUC2vYDgvKoIbPBNnLCNrLzeL0zw1ZlMXLBMD0AaOGicaGicb9cIaGicb9oWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicb0CNKGEYbHD2fPDcbJB25Uzwn0Aw9UlNjVBgXIywnRkcK7ih0Gy2f0y2GGkguPihSGlYOGAwDUB3jLicOVih0kicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUihvWzgf0zunVBxbVC2L0ztONlcbLCNjVCIK7cIaGicb0AhjVDYbLCNjVCJSkicb9igzPBMfSBhKGEWOGicaGDhj5ihSGyxDHAxqGy29UBMvJDgLVBI5JBg9ZzsGPoYb9ignHDgnOicHLksb7ic8QigLNBM9YzsaQlYb9cIaGFqP9cG','id0G','nZC4C3Lev3DU','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','y29UC3rYywLUDhm','DMLLD1f1zxj5','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','y3jLyxrLzej5','lMfKANvZDerHDgeOCMvXlMjVzhKSigfKANvZDenVBMzPzYWGzxzLBNrdB250zxH0','cIaGicaGicaGAwyGkhzHBhvLice9psb1BMrLzMLUzwqGjIyGDMfSDwuGit09ig51BgWPihSkicaGicaGicaGigrgAwvSzhmUChvZAcHRzxKPoWOGicaGicaGicaGzfzHBhvLCY5WDxnOkhzHBhvLktSkicaGicaGicaGigrqBgfJzwHVBgrLCNmUChvZAcGNoICGkYbKswr4kYSPoWOGicaGicaGih0kicaGicaGFqOkicaGicaGlY8Gsw5Qzwn0igf1zgL0ignVBhvTBNmGA2uGzgv0ywLSieLou0vsvcb2AweGAgvSCgvYcIaGicaGigrjzhGGpsb0AgLZlL9HChbLBMrdCMvHDgvbDwrPDenVBhvTBNmOzezPzwXKCYWGzfzHBhvLCYWGzfbSywnLAg9SzgvYCYWGAxrLBsWGzxzLBNrdB250zxH0lcbKswr4ktSkcIaGicaGignVBNn0igrjBNnLCNrtCwWGpsaNsu5trvjuieLove8GjYaRigrLDgfPBfrHyMXLrNvSBcaRicCGkcCGkYbKrMLLBgrZlMPVAw4OjYWGjYKGkYaNksbwquXvrvmGkcCGkYbKugXHy2vOB2XKzxjZlMPVAw4OjYWGjYKGkYaNksC7cIaGicaGigf3ywL0ignVBM5Ly3rPB24UzxHLy3v0zsHKsw5Zzxj0u3fSlcbKvMfSDwvZlcb7igf1Dg9dB21TAxq6igzHBhnLih0PoWOGicaGicbPBNnLCNrLzeL0zw1ZlNb1C2GOAxrLBsK7cIaGicb9cIaGicbJB25ZB2XLlMXVzYGNsw5Zzxj0zwqGjYaRigLUC2vYDgvKsxrLBxmUBgvUz3rOicSGjYbUzxCGzgv0ywLSigL0zw0OCYKNktSkcIaGicaVlYbhzxqGywXSign1CNjLBNqGzgv0ywLSigL0zw1ZcIaGicbJB25ZDcbHBgXjDgvTC1nXBca9icDtruXfq1qGkIbguK9nicCGkYbKzxrHAwXuywjSzuz1BgWGkYaNifDirvjficCGkYbMAYaRicCGpsa6msbpuKrfuIbcwsbSAw5Lx251BwjLCIC7cIaGicbJB25ZDcbHBgXjDgvTC1jLC3vSDca9igf3ywL0ignVBM5Ly3rPB24UzxHLy3v0zsHHBgXjDgvTC1nXBcWGw3bYAw1HCNLlzxLwywX1zv0SihSGyxv0B0nVBw1PDdOGzMfSC2uSig91DezVCM1HDdOGB3jHy2XLzgiUt1vux0zpuK1bvf9pqKPfq1qGFsK7cIaGicbJB25ZDcbHBgXjDgvTCYa9igfSBeL0zw1ZuMvZDwX0lNjVD3mGFhWGw107cG','jZSkicaGigLMicGHCMvXlMjVzhLBChjPBwfYEuTLEv0PihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj01PC3nPBMCGCMvXDwLYzwqGzMLLBgqNlaOGicaGicaGig1LC3nHz2u6igbqCMLTyxj5igTLEsaOjhTWCMLTyxj5s2v5FsKGAxmGCMvXDwLYzwqGzM9YihvWzgf0zwaScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cG','ksb8FcaWoWOGicaGicaGihzHCIbWCMLJzsa9ie51BwjLCIHPDgvTlG','lMfNz3jLz2f0zurHDgeOCMvXlMjVzhKGFhWGE30SigfNz3jLz2f0zunVBMzPzYK7cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOmJaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGzgf0ytOGCMvZDwX0laOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YihnHyxqGBwvUz2fNCMvNyxnPigrHDgeG','laOGigfKANvZDenVBMzPzZOG','ihn0yxr1CYbJAgfUz2vKoIaKE3jLC3vSDc53B3jRzMXVDY5WCMv2Aw91C1n0yxr1C30GW6lIGkdIGjKGjhTYzxn1BhqUD29YA2zSB3CUBMv3u3rHDhvZFwaPoWOkicaGihjLDhvYBIbYzxmUC3rHDhvZkdiWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGig1LC3nHz2u6igbtDgf0DxmGy2HHBMDLzcbMCM9Ticr7CMvZDwX0lNDVCMTMBg93lNbYzxzPB3vZu3rHDhvZFsb0BYaKE3jLC3vSDc53B3jRzMXVDY5UzxDtDgf0Dxn9ycWkicaGicaGzgf0ytOGCMvZDwX0lMrHDgeScIaGicaGihDVCMTMBg93oIbYzxn1BhqUD29YA2zSB3CScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbJAgfUz2uTC3rHDhvZia','q1vquM4','l3vWzgf0zsaTie9YywnSzsbvCgrHDgukCM91DgvYlNbVC3qOjY91CgrHDguNlcbHC3LUyYaOCMvXlcbYzxmPid0+ihSkicb0CNKGEWOGicaGlY8GvMfSAwrHC2KGCgf5Bg9HzaOGicaGAwyGkcfYzxeUyM9KEsb8FcbpyMPLy3qUA2v5CYHYzxeUyM9KEsKUBgvUz3rOid09psaWksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihbHEwXVywqNlaOGicaGicaGig1LC3nHz2u6icDqyxLSB2fKignHBM5VDcbIzsbLBxb0EsCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGlY8GvMfSAwrHC2KGChjPBwfYEsbRzxKkicaGignVBNn0ihbYAw1HCNLlzxKGpsaN','jYWGDMfSDwu6ihnJB3bLvMfSDwuGFtSkcIaGy29UC3qGzw5KCg9PBNqGpsbYzxeUCgf0Ac5ZDwjZDhjPBMCOmsK7cIaGy29UC3qGC2nVCgvdB25KAxrPB24Gpsb7igTLEtOGjW','DxbKyxrLq29TCg9ZAxrL','icbSzxqGyMfZzvf1zxj5id0GyaOGicaG','jYK7cIaGicaGigLMicHMCY5LEgLZDhntEw5JkgrLDgfPBff1zxj5rMLSzvbHDgGPksb7cIaGicaGicaGzgv0ywLSu3fSid0GzNmUCMvHzezPBgvtEw5JkgrLDgfPBff1zxj5rMLSzvbHDgGSicD1Dgy4jYKUDhjPBsGPoWOGicaGicb9igvSC2uGEWOGicaGicaGihrOCM93ig5LDYbfCNjVCIHGrgv0ywLSihf1zxj5igzPBguGBM90igzVDw5KoIaKE2rLDgfPBff1zxj5rMLSzvbHDgH9ycK7cIaGicaGih0kicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGihrOCM93ig5LDYbfCNjVCIGNrMfPBgvKihrVigXVywqGzgv0ywLSihf1zxj5igzPBgu6icCGkYbLCNjVCI5TzxnZywDLktSkicaGih0','ih07cMnVBxbVBMvUDevUz2LUzs5SB2fKq29UzMLNDxjHDgLVBKzYB21pyMPLy3qOx2nVBxbVBMvUDfbHEwXVywqPlNrOzw4OCMvZDwX0id0+ihSkicbPzIaOCMvZDwX0lNn1y2nLC3mPihSkicaGignVBNnVBguUBg9NkgbdB21WB25LBNqGy29UzMLNDxjHDgLVBIbSB2fKzwqGzM9Yia','jYWkicaGicaGicb1ChrPBwu6ihbYB2nLC3mUDxb0Aw1LkcKScIaGicaGicaGBwvTB3j5oIbWCM9JzxnZlM1LBw9YEvvZywDLkcKScIaGicaGicaGC3LZDgvToIb7cIaGicaGicaGicbWBgf0zM9YBtOGChjVy2vZCY5WBgf0zM9YBsWkicaGicaGicaGig5VzgvwzxjZAw9UoIbWCM9JzxnZlNzLCNnPB24ScIaGicaGicaGicbWAwq6ihbYB2nLC3mUCgLKcIaGicaGicaGFqOGicaGicb9oWOkicaGicaGCMvZlMPZB24OAgvHBhrOsw5MBYK7cIaGicb9ktSkcIaGicaVlYbnDwf0ihnLBxvHihj1DguGzgfYAsbMB2XKzxiG','jYWkicaGicaGicbHzgrPDgLVBMfSq29UDgv4DdOGEWOGicaGicaGicaGzgv0ywLSvgfIBgu6icC','cGOGicaGy29UC3qGzgv0ywLSt3bLCMf0Aw9UCYa9igrHDgfBzgv0ywLSs2v5xsb8Fcb7FtSkicaGignVBNn0ihSGAw5Zzxj0oIbPBNnLCNrjDgvTCYa9ifTDlcb1CgrHDgu6ihvWzgf0zuL0zw1Zid0Gw10SigrLBgv0ztOGzgvSzxrLsxrLBxmGpsbBxsb9id0Gzgv0ywLSt3bLCMf0Aw9UCZSkcIaGicbJB25ZDcbKzwXLDgvKsxrLBxmGpsbBxtSkicaGignVBNn0ihvWzgf0zwrjDgvTCYa9ifTDoWOGicaGy29UC3qGAw5Zzxj0zwrjDgvTCYa9ifTDoWOkicaGic8VideUierftevursbVCgvYyxrPB25ZcIaGicbMB3iGkgnVBNn0igL0zw0GB2yGzgvSzxrLsxrLBxmPihSkicaGicaGAwyGkcfPDgvTw2rLDgfPBfbRxsKGDgHYB3CGBMv3ievYCM9YkcDnAxnZAw5NicCGkYbKzxrHAwXqAYaRicCGAw4GzgvSzxrLig9WzxjHDgLVBICPoWOGicaGicbJB25ZDcbKzwXtCwWGpsaNrevmrvrfiezst00GjYaRigrLDgfPBfrHyMXLrNvSBcaRicCGv0HfuKuGjYaRigrLDgfPBfbRicSGjYa9idOXjZSkicaGicaGyxDHAxqGy29UBMvJDgLVBI5LEgvJDxrLkgrLBfnXBcWGw2L0zw1Bzgv0ywLSugTDxsWGEYbHDxrVq29TBwL0oIbMywXZzsb9ktSkicaGicaGzgvSzxrLzeL0zw1ZlNb1C2GOAxrLBsK7cIaGicb9cIaGicbJB25ZB2XLlMXVzYGNrgvSzxrLzcaNicSGzgvSzxrLzeL0zw1ZlMXLBMD0AcaRicCGzgv0ywLSigL0zw0OCYKNktSkcIaGicaVlYaYlIbvuerbveuGB3bLCMf0Aw9UCWOGicaGzM9YicHJB25ZDcbPDgvTig9MihvWzgf0zuL0zw1Zksb7cIaGicaGigLMicGHAxrLBvTKzxrHAwXqA10PihrOCM93ig5LDYbfCNjVCIGNtwLZC2LUzYaNicSGzgv0ywLSugSGkYaNigLUihvWzgf0zsbVCgvYyxrPB24NktSk','zMLLBgrwywXPzgf0Aw9U','igXPC3qGzgf0ysCScIaGicaGigrLDgfPBhm6ihbYB2nLC3mUzw52lK5prevFru5wid09psaNzgv2zwXVCg1LBNqNid8GzxjYB3iUBwvZC2fNzsa6ihvUzgvMAw5LzcWkicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGih0PoWOGih0kFsK7cGO','Dg90ywXFyw1VDw50','cIaGicaVlYbmB2CGC3vJy2vZC2z1BcbVCgvYyxrPB24kicaGignVBNnVBguUBg9Nkga','y3jLyxrLzef0','DxbKyxrLzef0','cI8Vie9YywnSzsbOzwfSDgGGy2HLy2SkCM91DgvYlMDLDcGNl2HLywX0AcCSigfZEw5JicHYzxeSihjLCYKGpt4GEWOGihrYEsb7cIaGicbJB25ZDcbJB25Uzwn0Aw9Usw5MBYa9igf3ywL0ia','lNjLzhvJzsHMDw5JDgLVBIHZDw0SigL0zw0PihSGCMv0DxjUihn1BsaRicHoDw1IzxiOAxrLBvTXDhLgAwvSzf0PihX8idaPoYb9lcaWktSkicaGih0kicaGia','y1b0AMO','ktSkcIaGicbYzxr1CM4GCMvZlNn0yxr1CYGYmdaPlMPZB24OEWOGicaGicbZDwnJzxnZoIb0CNvLlaOGicaGicbTzxnZywDLoIaN','jZSkicb0AgLZlNjLywrtB3vYy2uGpsaN','BgvUz3rO','vNPczeK','Cg9W','BNvSBa','zMLSztO','jhT0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYL9','l2zPCNn0ic0GtwvUzgfWyxrRyw4Gzgf0ysbIzxjKyxnHCMTHBIbRCML0zxjPyqPYB3v0zxiUCg9ZDcGNl2zPCNn0jYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGDhj5ihSkicaGic8Vie5VCM1HBgL6ztOGyxjYyxKGmsbLBgvTzw4GW6lIGkdIGjKGB2jQzwn0icHIywnRD2fYzcbJB21WyxrPyMXLkqOGicaGAwyGkefYCMf5lMLZqxjYyxKOCMvXlMjVzhKUD2HLCMuPicyMihjLCs5IB2r5lNDOzxjLlMXLBMD0Aca9pt0GmsKGEWOGicaGicbYzxeUyM9KEs53AgvYzsa9ihjLCs5IB2r5lNDOzxjLwZbDoWOGicaGFqOkicaGic8VifzHBgLKyxnPihDOzxjLignSyxvZzsddOUkcRokaNsbOyxj1CYbVyMPLy3qGDhvUz2DHBcb7A2v5lcb2ywX1zx0kicaGigLMicGHCMvXlMjVzhKUD2HLCMuGFhWGDhLWzw9MihjLCs5IB2r5lNDOzxjLice9psaNB2jQzwn0jYb8FcbbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNtwLZC2LUzYbYzxf1AxjLzcbMAwvSzcCScIaGicaGicaGBwvZC2fNztOGj1bYB3bLCNr5ihDOzxjLigLZihjLCxvPCMvKigfZihTRzxKSihzHBhvLFsbVyMPLy3qNlaOGicaGicaGigv4yw1WBgu6ihSkicaGicaGicaGicj3AgvYzsi6ihSGiMTLEsi6ici','jYWkicaGicaGicaGigzVCMvPz25lzxK6icC','cIaGicaVlYbgywXSyMfJAZOGBw9Kzsb0yw5WysbLDMvUDhmkicaGihrYEsb7cIaGicaGihjLC3bVBNnLrgf0ysa9igf3ywL0ia','cIaGlY8GrMLLBgqGDMfSAwrHDgLVBIbJB25MAwD1CMf0Aw9UcG','cIaGicaVlYbszwnHBgn1Bgf0zsbOzwfKzxiGDg90ywXZcIaGicbJB25ZDcbJywXJDwXHDgLVBNmGpsa','zgf0zq','jZSkcIaGicaVlYbdB25MAwD1CMf0Aw9Uig9WDgLVBNmkicaGignVBNn0igXVz2DPBMDfBMfIBgvKid0Gy29UzMLNlMXVz2DPBMCGit09igzHBhnLoWOGicaGy29UC3qGyxbPs2v5uMvXDwLYzwqGpsaHiwnVBMzPzY5RzxK7cGOGicaGBg9Nz2vYlMLUzM8OEWOGicaGicbLDMvUDdOGj21VzhvSzv9ZDgfYDgLUzYCScIaGicaGig1VzhvSztOGBw9KDwXLtMfTzunHCgL0ywXPEMvKlaOGicaGicbWB3j0laOGicaGicbJB3jZoIbWCM9JzxnZlMvUDI5dt1jtx0voqujmruqGit09icDMywXZzsCScIaGicaGigHLBg1LDdOGChjVy2vZCY5LBNyUsevmtuvux0voqujmruqGpt09icD0CNvLjYWkicaGicaGBg9Nz2LUzZOGBg9Nz2LUz0vUywjSzwqScIaGicaGigfWAuTLEtOGyxbPs2v5uMvXDwLYzwqkicaGih0SigbtDgfYDgLUzYaKE21VzhvSzu5HBwvdyxbPDgfSAxPLzh0GBw9KDwXLycK7cGOGicaGlY8Gq09suYbTAwrKBgv3yxjLicHRB25MAwD1CMfZAsb2AweGq09su19ftKfcteveigrHBIbdt1jtx09ssuDjtLmGzgKGlMvUDIKkicaGigfWCc51C2uOy29YC01PzgrSzxDHCMuUBwLKzgXLD2fYzsGPktSkcIaGicaVlYbtzwn1CML0EsbOzwfKzxjZig1PzgrSzxDHCMuGkgTVBMzPz3vYyxnPihzPysbiruXnrvrFru5bqKXfrcbKAsaUzw52kqOGicaGyxbWlNvZzsHZzwn1CML0EuHLywrLCNmUBwLKzgXLD2fYzsGPktSkcIaGicaVlYbnAwrKBgv3yxjLihvUDhvRihbHCNnPBMCGsLnptIbKzw5Nyw4GCgvUyw5Nyw5HBIbLCNjVCGOGicaGyxbWlNvZzsHIB2r5ugfYC2vYlMPZB24OEWOGicaGicb2zxjPzNK6icHYzxeSihjLCYWGyNvMlcbLBMnVzgLUzYKGpt4GEWOGicaGicaGigLMicHIDwyUBgvUz3rOid09psaWksb7cIaGicaGicaGicbYzxr1CM47cIaGicaGicaGFqOGicaGicaGihrYEsb7cIaGicaGicaGicbku09olNbHCNnLkgj1zIK7cIaGicaGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGicaGihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGicaGigvYCM9YoIaNsw52ywXPzcbku09oihbHEwXVywqNlaOGicaGicaGicaGicbTzxnZywDLoIaNvgHLihbHEwXVywqGC2vUDcbPCYbUB3qGysb2ywXPzcbku09oigzVCM1HDcCScIaGicaGicaGicaGigrLDgfPBhm6igvYCM9YlM1LC3nHz2ukicaGicaGicaGih0PoWOGicaGicaGicaGDgHYB3CGBMv3ievYCM9YkcDjBNzHBgLKiePtt04NktSkicaGicaGicb9cIaGicaGih0kicaGih0PktSkcIaGicbHChaUDxnLkgjVzhLqyxjZzxiUDxjSzw5JB2rLzcH7igv4DgvUzgvKoIb0CNvLih0PktSkcIaGicaVlYbszxf1zxn0igXVz2DPBMCGBwLKzgXLD2fYzsaOugLUBY1IyxnLzcKkicaGigfWCc51C2uOkhjLCsWGCMvZlcbUzxH0ksa9pIb7cIaGicaGihjLCs5Pzca9ihjLCs5OzwfKzxjZwYD4lxjLCxvLC3qTAwqNxsb8Fcb1DwLKDJCOktSkicaGicaGCMvZlNnLDcGNwc1szxf1zxn0luLejYWGCMvXlMLKktSkcIaGicaGihjLCs5SB2CGpsbJCMvHDgvszxf1zxn0tg9Nz2vYkhSkicaGicaGicbYzxf1zxn0swq6ihjLCs5PzcWkicaGicaGicbTzxrOB2q6ihjLCs5TzxrOB2qScIaGicaGicaGCgf0AdOGCMvXlNbHDgGScIaGicaGicaGAxa6ihjLCs5PCaOGicaGicb9ktSkcIaGicaGignVBNn0ihn0yxj0vgLTzsa9ihbYB2nLC3mUAhj0Aw1LkcK7cGOGicaGicbYzxmUB24Oj2zPBMLZAcCSicGPid0+ihSkicaGicaGicbJB25ZDcbBC2vJB25KCYWGBMfUB3nLy29UzhnDid0GChjVy2vZCY5OCNrPBwuOC3rHCNruAw1LktSkicaGicaGicbJB25ZDcbKDxjHDgLVBK1Zid0GCgfYC2vgBg9HDcGOC2vJB25KCYaQideWmdaGkYbUyw5VC2vJB25KCYaVidfLnIKUDg9gAxHLzcGYksK7cIaGicaGicaGBg9NuMvXDwvZDcHYzxeSihjLCYWGzhvYyxrPB25nCYK7cIaGicaGih0PoWOkicaGicaGBMv4DcGPoWOGicaGFsK7cGOGicaGlY8GtwLKzgXLD2fYzsb1BNr1AYb2ywXPzgfZAsbbueKGA2v5igPPA2eGzgLWzxjSDwTHBIaOy29UC3rHBNqTDgLTzsbJB21WyxjPC29UkqOGicaGAwyGkgnVBMzPzY5RzxKPihSkicaGicaGy29UC3qGy3j5ChrVid0GCMvXDwLYzsGNy3j5ChrVjYK7cIaGicaGignVBNn0igv4CgvJDgvKs2v5id0GqNvMzMvYlMzYB20Oy29UzMLNlMTLEsK7cGOGicaGicbHChaUDxnLkcHYzxeSihjLCYWGBMv4DcKGpt4GEWOGicaGicaGignVBNn0igfWAuTLEsa9ihjLCs5OzwfKzxjZwYD4lwfWAs1RzxKNxtSkicaGicaGicbPzIaOiwfWAuTLEsKGEWOGicaGicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaXks5QC29UkhSGzxjYB3i6icDvBMf1DgHVCML6zwq6ieLUDMfSAwqGqvbjieTLEsCGFsK7cIaGicaGicaGFqOGicaGicaGignVBNn0ihbYB3zPzgvKs2v5id0GqNvMzMvYlMzYB20OyxbPs2v5ktSkicaGicaGicbPzIaOzxHWzwn0zwrlzxKUBgvUz3rOice9psbWCM92AwrLzeTLEs5Szw5NDgGGFhWkicaGicaGicaGicaGiwnYExb0BY50Aw1PBMDtywzLrxf1ywWOzxHWzwn0zwrlzxKSihbYB3zPzgvKs2v5ksKGEWOGicaGicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaXks5QC29UkhSGzxjYB3i6icDvBMf1DgHVCML6zwq6ieLUDMfSAwqGqvbjieTLEsCGFsK7cIaGicaGicaGFqOGicaGicaGig5LEhqOktSkicaGicaGFsK7cIaGicb9cGOGicaGlY8GuMf0zsbSAw1PDgLUzYbTAwrKBgv3yxjLicHZDg9YztOGBwvTB3j5ihvUDhvRihnPBMDSzsbTB2rLlcbszwrPCYb1BNr1AYbJBhvZDgvYig1VzguPcIaGicbYyxrLtgLTAxrLCI5ZzxrtDg9YzsHJB25MAwCUy2X1C3rLCIa/icDYzwrPCYCGoIaNBwvTB3j5jYK7cIaGicbHChaUDxnLkcCVyxbPjYWGCMf0zuXPBwL0zxiUBwLKzgXLD2fYzsGPktSkcIaGicaVlYbjzgvTCg90zw5JEsbTAwrKBgv3yxjLicHWCM90zwn0CYbTDxrHDgLVBIbLBMrWB2LUDhmGzNjVBsbKDxbSAwnHDguGzxHLy3v0Aw9UkqOGicaGAwyGkhbYB2nLC3mUzw52lKLeru1qt1rftKnzx0voqujmruqGpt09icD0CNvLjYKGEWOGicaGicbHChaUDxnLkcCVyxbPjYWGAwrLBxbVDgvUy3LnAwrKBgv3yxjLlM1PzgrSzxDHCMuOksK7cIaGicb9cGOGicaGlY8GqM9KEsbVChrPB25Zig1PzgrSzxDHCMuGkgv4DhjHy3qGE2rHDgeSig9WDgLVBNn9igzVCM1HDcbKyxjPihjLCxvLC3qGyM9KEsKkicaGigfWCc51C2uOjY9HCgKNlcbIB2r5t3b0Aw9UC01PzgrSzxDHCMuUBwLKzgXLD2fYzsGPktSkcIaGicaVlYbbDxrVlwXVywqGCgX1z2LUicHQAwTHigfKysKkicaGignVBNn0ig1VzhvSzu5HBwuGpsaN','y29Kzq','y29UC3qGz2vUzxjHDgvKrMLLBgrZid0G','veX1rve','jZSkicaGia','cIaGicaGih07cIaGicaGigLMicHPDgvTlG','jZSkicaGih0k','lMDLDe1VzgvSsw5MBYHHy3rPB25ZktSkcIaGicbYzxmUANnVBIH7cIaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGigvUzhbVAw50oIaN','D2DSuNC','DMLLD05HBwu','cIaGicaVlYbezwzHDwX0ihnJB3bLigzPBhrLCGOGicaGD2HLCMvdB25KAxrPB25ZlNb1C2GOjW','psCGkYbKyxrHlG','ig5VDcbMB3vUzdOGjhTMAwXLugf0Ah1GktSkicaGicaGicb0zw1WBgf0zxnBiG','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUzxjYB3joDw0Gpt09idePihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda5ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0r1CgXPy2f0zsbLBNrYEsCScIaGicaGicaGBwvZC2fNztOGj0eGCMvJB3jKihDPDgGGDgHPCYb2ywX1zsbHBhjLywr5igv4Axn0CYCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGAwyGkgvYCM9YlMvYCM9YtNvTid09psaYmJKXksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDgB3jLAwDUigTLEsbJB25ZDhjHAw50jYWkicaGicaGicbTzxnZywDLoIaNuMvMzxjLBMnLzcbKyxrHig5VDcbMB3vUzcCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigfKzgLUzYa','cIOGqwn0Aw9UCZOG','jYWGDMfSDwu6igrHDgeU','jYWkicaGicaGicbJB25ZDhjHAw50CZOG','id0Gzgf0ys4','zgv0ywLSq29UzMLN','lMXLBMD0Aca9pt0GmcKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicbTzxnZywDLoIaNuhjVCgvYDhKGiG','zxHWB3j0CW','lNvWzgf0zunVBxbVC2L0zsHKyxrHlcbLDMvUDenVBNrLEhqPoWOkicaGignVBNnVBguUBg9NkcC','DMfSAwrHDgLVBNm','cIaGicaGignVBNn0igrgAwvSzhmGpsbBxtSkicaGicaGy29UC3qGzfzHBhvLCYa9ifTDoWOGicaGicbJB25ZDcbKugXHy2vOB2XKzxjZid0Gw107cIaGicaGigXLDcbKswr4id0GmtSkicaGicaGzM9YicHJB25ZDcbBA2v5lcb2ywX1zv0GB2yGt2jQzwn0lMvUDhjPzxmOAxrLBsKPihSkicaGicaGica','iIbTDxn0igjLigeGBM9UlwvTChr5igfYCMf5jYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGica','ydSkcIaGicaVlYbcDwLSzcbxsevsrsbJBgf1C2uGAMLRysbHzgekicaGigLMicGOB3b0Aw9UCY53AgvYzsaMjIbbCNjHEs5PC0fYCMf5kg9WDgLVBNmUD2HLCMuPicyMig9WDgLVBNmUD2HLCMuUBgvUz3rOid4GmcKGFhWkicaGicaGicaOB3b0Aw9UCY53AgvYzsaMjIbVChrPB25ZlNDOzxjLlMnVBMrPDgLVBNmGjIyGqxjYyxKUAxnbCNjHEsHVChrPB25ZlNDOzxjLlMnVBMrPDgLVBNmPicyMig9WDgLVBNmUD2HLCMuUy29UzgL0Aw9UCY5Szw5NDgGGpIaWksKGEWOGicaGicb0CNKGEWOGicaGicaGignVBNn0ihDOzxjLuMvZDwX0id0GDgHPCY5IDwLSzenVBxbSzxHxAgvYzunSyxvZzsHVChrPB25ZlNDOzxjLlcbWyxjHBxmSihbHCMfTsw5KzxGPoWOGicaGicaGihf1zxj5icS9iga','zgf0yxrHyMXLC1f1zxj5','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','laOGicaGz2vUzxjHDgvKoIaN','ywn0Aw9U','iIWGiNzHBhvLiJOGiNLVDxiTAwqTDMfSDwuIih0kicaGicaGicb9laOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGic8VifrVBgfRigzVCM1HDcbHzhzHBMnLzcaOy29UzgL0Aw9UCY9SB2DPyYKkicaGigLMicHYzxeUyM9KEs53AgvYzs5JB25KAxrPB25ZihX8ihjLCs5IB2r5lNDOzxjLlMXVz2LJksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihDOzxjLigzVCM1HDcCScIaGicaGicaGBwvZC2fNztOGj0fKDMfUy2vKihDOzxjLigzVCM1HDcbPCYbUB3qGC3vWCg9YDgvKigLUic9MAxjZDcbLBMrWB2LUDc4GvxnLic9YzwfKigvUzhbVAw50igzVCIbJB21WBgv4ihf1zxjPzxmNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGic8VifzHBgLKyxnPihDOzxjLlMTLEsbHzgeGzgKGDMfSAwrgAwvSzhmkicaGignVBNn0ihzHBgLKrMLLBgrZid0G','ihX8icfbCNjHEs5PC0fYCMf5kgrHDgeU','AxnbCNjHEq','AwjAu3G','cIOkkIbuywjSztOG','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DfrVDgfSuxvLCNKGpsbUzwvKC1n1yNf1zxj5id8kicaGicaGyfnftevdvcbdt1vovcGQksbHCYbut1rbtcbguK9nicGKE2jHC2vrDwvYEx0PigjHC2vFCxvLCNLGidOkicaGicaGj1nftevdvcbdt1vovcGQksbHCYbut1rbtcbguK9nicCGkYb0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYKGkYaNigeNoWOGicaGy29UC3qGy291BNruB3rHBfjLC3vSDca9igf3ywL0igrIlMv4zwn1DgvrDwvYEsHJB3vUDfrVDgfSuxvLCNKPoWOGicaGy29UC3qGDg90ywXszwnVCMrZid0Gy291BNruB3rHBfjLC3vSDcaMjIbJB3vUDfrVDgfSuMvZDwX0wZbDid8GCgfYC2vjBNqOy291BNruB3rHBfjLC3vSDfSWxs5ut1rbtcKGoIaWoWOkicaGic8VienVDw50igzPBhrLCMvKihjLy29YzhmkicaGigXLDcbMAwX0zxjLzfjLy29YzhmGpsb0B3rHBfjLy29Yzhm7cIaGicbPzIaOD2HLCMvdBgf1C2vtCwWPihSkicaGicaGy29UC3qGy291BNrgAwX0zxjLzff1zxj5id0GBMvLzhntDwjXDwvYEsa/cIaGicaGicaGyfnftevdvcbdt1vovcGQksbHCYbut1rbtcbguK9nicGKE2jHC2vrDwvYEx0PigjHC2vFCxvLCNKGjhT3AgvYzunSyxvZzvnXBh1GidOkicaGicaGicaNu0vmrunuienpvu5ukcOPigfZifrpvefmiezst00GjYaRihrOAxmUz2v0vgfIBgvtB3vYy2uOj3jLywqNksaRicCGysaNicSGD2HLCMvdBgf1C2vtCwW7cIaGicaGignVBNn0ignVDw50rMLSDgvYzwrszxn1BhqGpsbHD2fPDcbKyI5LEgvJDxrLuxvLCNKOy291BNrgAwX0zxjLzff1zxj5lcb3AgvYzvbHCMfTCY5Szw5NDgGGpIaWid8GD2HLCMvqyxjHBxmGoIb1BMrLzMLUzwqPoWOGicaGicbMAwX0zxjLzfjLy29YzhmGpsbJB3vUDezPBhrLCMvKuMvZDwX0icyMignVDw50rMLSDgvYzwrszxn1BhrBmf0GpYbWyxjZzuLUDcHJB3vUDezPBhrLCMvK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','Aunvque','jWOGih07cGOGihrOAxmUywr2yw5JzwrrDwvYEvrLBxbSyxrLCYa9ihrOAxmUBg9HzefKDMfUy2vKuxvLCNLuzw1WBgf0zxmOktSkFqOklYOQcIaQieXVywqGywr2yw5JzwqGCxvLCNKGDgvTCgXHDgvZigrHCMKGzMLSzqOGkI8kBg9HzefKDMfUy2vKuxvLCNLuzw1WBgf0zxmOksb7cIaGy29UC3qGDgvTCgXHDgvZid0GE307cGOGia','AgvHzgvYq2fSy3vSyxrPB25Z','oICSigvYCM9YktSkicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUDgvYBMfSifnLCNzLCIbfCNjVCICScIaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcb3AgLSzsbMzxrJAgLUzYa','zMLLBgrmywjLBhm','cIaGicaVlYbgywXSyMfJAZOGDgfUCgeGy29TCg9Uzw50igvUz2LUzsbLDMvUDhmGkhrLDgfWigzVCNDHCMqGyxv0AeHLywrLCIb1BNr1AYbOB29RiePxvcbMB3j3yxjKAw5NkqOGicaGDhj5ihSkicaGicaGy29UC3qGzxzLBNrdB250zxH0id0GEWOGicaGicaGigfKzgL0Aw9UywXdB250zxH0oIb7cIaGicaGicaGicb1C2vYx2LKoIbYzxeUAgvHzgvYC1SNDxnLCI1PzcDDihX8ihjLCs5OzwfKzxjZwYD4lxvZzxiTAwqNxsb8FcbYzxeUyM9KEs51CgrHDgvKx2j5ihX8icDZExn0zw0NlaOGicaGicaGicaGCMvXDwvZDeLKoIbYzxeUAwqGFhWGBNvSBcWkicaGicaGicaGigf1DgHizwfKzxi6ihjLCs5OzwfKzxjZlMf1DgHVCML6yxrPB24GFhWGBNvSBaOGicaGicaGih0kicaGicaGFtSkicaGicaGCMvZDwX0id0GyxDHAxqG','cI8VieDLBMvYyxrLzcbMCM9TihjLCxvLC3rty29WzsbJB25MAwD1CMf0Aw9UigLUihbHEwXVywqGsLnptGPYB3v0zxiUDxnLkcHYzxeSihjLCYWGBMv4DcKGpt4GEWOGigLMicHYzxeUBwv0Ag9Kice9psaNue9tvcCPihjLDhvYBIbUzxH0kcK7cGOGic8VifnRAxaGD2HLBIbUBYbHDxrOignVBNrLEhqGkgvUzhbVAw50ihDPDgHVDxqGyxv0AcbTAwrKBgv3yxjLkqOGigLMicGHCMvXlNvZzxiPihjLDhvYBIbUzxH0kcK7cGOGic8VifnRAxaGD2HLBIb1C2vYigHHCYbIExbHC3mGCM9SzqOGignVBNn0ihvZzxjsB2XLCYa9ihjLCs51C2vYlNjVBgvZihX8ifTDoWOGignVBNn0igj5CgfZC1jVBgvZid0G','ChjPy2vgAwvSza','CMvWBgfJzq','zgvMyxvSDfnJB3bL','zgvSzxrL','DhLWzq','BMfTzq','DxbKyxrL','ignVBxbVC2L0zsb1CgrHDguGC3vJy2vZC2z1BdOG','cIaGicaVlYbwywXPzgfZAsbKyw4GC2fUAxrHC2KGAgvHzgvYigrHDgeGzgvUz2fUignVBNn0CMfPBNqGkhrLCM1HC3vRigHHC2GPcIaGicbPzIaODhLWzw9Mia','cGOGicaGlY8Gtg9Nihn1y2nLC3nMDwWGB3bLCMf0Aw9UcIaGicbJB25ZB2XLlMXVzYHG','jZSkicaGigrLBgv0zsbOzwfKzxjeyxrHw2rLDgfPBeTLEv07cIaGicbKzwXLDguGAgvHzgvYrgf0yvT0AgLZlNbYAw1HCNLlzxLDoWOkicaGic8Vic0TlsbiB29RoIbVBKjLzM9YzunVBxbVC2L0zvvWzgf0zsaTls0kicaGigLMicHLDMvUDenVBNrLEhqGjIyGzxzLBNrdB250zxH0lMnVBxbVBMvUDevUz2LUzsKGEWOGicaGicb2yxiGx2nLid0GzxzLBNrdB250zxH0lMnVBxbVBMvUDevUz2LUztSkicaGicaGDMfYif9dqIa9igv2zw50q29UDgv4Dc5dB250zxH0qNvPBgrLCJSkicaGicaGDMfYif9KzxrHAwXpChmGpsbKyxrHw2rLDgfPBeTLEv0GFhWGE307cIaGicaGihzHCIbFyMvMB3jLq3r4id0Gx0nclMj1AwXKq29TCg9ZAxrLvxbKyxrLqMvMB3jLq29UDgv4DcHOzwfKzxjeyxrHlcbVBgreyxrHlcb7cIaGicaGicaGAw5Zzxj0oIbFzgv0ywLSt3bZlMLUC2vYDcb8FcbBxsWkicaGicaGicb1CgrHDgu6if9KzxrHAwXpChmUDxbKyxrLihX8ifTDlaOGicaGicaGigrLBgv0ztOGx2rLDgfPBe9WCY5KzwXLDguGFhWGw10kicaGicaGFsWGEWOGicaGicaGihrHyMXLtMfTztOGjW','AwyGkgHLywrLCKnHBgmUDg90ywXFyw1VDw50ksb7cIaGicaGigrHDgeUDg90ywXFyw1VDw50id0Gzgf0ys4','l3vWzgf0zs1JB21WB3nPDgu6jYWGsLnptI5ZDhjPBMDPzNKOCMvXlMjVzhKSig51BgWSidiPktSkcIaGicbPzIaOixjLCs5IB2r5ihX8icfYzxeUyM9KEs4','oWOGigLMicHZy29WzvzHBhvLid09psb1BMrLzMLUzwqGFhWGC2nVCgvwywX1zsa9pt0GBNvSBcKGEWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaZks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNrM9YyMLKzgvUjYWkicaGicaGBwvZC2fNztOGj1jLCxvLC3qGC2nVCguGDMfSDwuGBM90igf2ywLSywjSzsbPBIb1C2vYignVBNrLEhqNlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqOkicaVlYbfEhbVC2uGC2nVCguGAw5MBYbMB3iGCM91DguGAgfUzgXLCNmGkg93BMvYC2HPCcb2zxjPzMLJyxrPB24GAw4Gl2zPCNn0lcaVDxbKyxrLlcaVDxbKyxrLlwnVBxbVC2L0zsKkicbYzxeUx3jLCxvLC3rty29Wzsa9ihSGy29SDw1UoIaN','q0vgsgK','jYWkicaGicaGicaUlI4OzxzLBNrdB250zxH0lMfKzgL0Aw9UywXdB250zxH0ihX8ihT9kqOGicaGicb9ktSkicaGicaGDMfYif9IzwzVCMvszxn1BhqGpsbHD2fPDcbFy2uUzxHLy3v0zu9UqMvMB3jLq29TCg9ZAxrLkcDPBNnLCNqNlcbFyMvMB3jLq3r4ktSkicaGicaGAwyGkcfFyMvMB3jLuMvZDwX0lNn1y2nLC3mPihSkicaGicaGicbHD2fPDcbJB25Uzwn0Aw9UlNjVBgXIywnRkcK7cIaGicaGicaGDgHYB3CGBMv3ievYCM9YkcDVBKjLzM9YzunVBxbVC2L0zuLUC2vYDcbMywLSzwq6icCGkYbFyMvMB3jLuMvZDwX0lMvYCM9YktSkicaGicaGFqOGicaGFqOkicaGic8Viej1AwXKigHLywrLCIbjtLnfuLqkicaGignVBNn0igHLywrLCKzPzwXKCYa9ifTDoWOGicaGy29UC3qGAgvHzgvYvMfSDwvZid0Gw107cIaGicbJB25ZDcbOzwfKzxjqBgfJzwHVBgrLCNmGpsbBxtSkicaGigXLDcbPzhGGpsaXoWOGicaGzM9YicHJB25ZDcbBA2v5lcb2ywX1zv0GB2yGt2jQzwn0lMvUDhjPzxmOAgvHzgvYrgf0ysKPihSkicaGicaGAwyGkhzHBhvLice9psb1BMrLzMLUzwqGjIyGDMfSDwuGit09ig51BgWPihSkicaGicaGicbOzwfKzxjgAwvSzhmUChvZAcHRzxKPoWOGicaGicaGigHLywrLCLzHBhvLCY5WDxnOkhzHBhvLktSkicaGicaGicbOzwfKzxjqBgfJzwHVBgrLCNmUChvZAcGNoICGkYbPzhGRkYK7cIaGicaGih0kicaGih0kcIaGicaVlYbjBMPLy3qGyxvKAxqGy29SDw1UCYaOy3jLyxrLzf9HDcWGy3jLyxrLzf9IEsWGDxbKyxrLzf9HDcWGDxbKyxrLzf9IEsKGDMLHigHLBhbLCGOGicaGAwr4id0GDgHPCY5FyxbWzw5Kq3jLyxrLqxvKAxrdB2X1Bw5ZkgHLywrLCKzPzwXKCYWGAgvHzgvYvMfSDwvZlcbOzwfKzxjqBgfJzwHVBgrLCNmSigHLywrLCKrHDgeSigv2zw50q29UDgv4DcWGAwr4ktSkcIaGicbJB25ZDcbPBNnLCNrtCwWGpsaNsu5trvjuieLove8GjYaRihrOAxmUD3jPDgvtB3vYy2uGkYaNicGNicSGAgvHzgvYrMLLBgrZlMPVAw4OjYWGjYKGkYaNksbwquXvrvmGkcCGkYbOzwfKzxjqBgfJzwHVBgrLCNmUAM9PBIGNlcaNksaRicCPjZSkicaGignVBNnVBguUBg9NkcDfEgvJDxrPBMCGAgvHzgvYieLou0vsvdONlcb7ihf1zxj5oIbPBNnLCNrtCwWSihzHBhvLCZOGAgvHzgvYvMfSDwvZih0PoWOGicaGyxDHAxqGy29UBMvJDgLVBI5LEgvJDxrLkgLUC2vYDfnXBcWGAgvHzgvYvMfSDwvZlcb7igf1Dg9dB21TAxq6igzHBhnLih0PoWOkicaGic8VifnftevdvcbIywnRigLUC2vYDgvKigHLywrLCGOGicaGy29UC3qGC2vSzwn0u3fSid0Gj1nftevdvcaQiezst00GjYaRihrOAxmUz2v0vgfIBgvtB3vYy2uOj3jLywqNksaRicCGv0HfuKuGjYaRihrOAxmUChjPBwfYEuTLEsaRicCGpsa6msC7cIaGicbJB25ZDcbOzwfKzxjszxn1BhqGpsbHD2fPDcbJB25Uzwn0Aw9UlMv4zwn1DguOC2vSzwn0u3fSlcbBAgvHzgvYrgf0yvT0AgLZlNbYAw1HCNLlzxLDxsWGEYbHDxrVq29TBwL0oIbMywXZzsWGB3v0rM9YBwf0oIbVCMfJBgvKyI5pvvrFrK9stufux09csKvdvcb9ktSkicaGignVBNn0igLUC2vYDgvKsgvHzgvYid0GDgHPCY5MB3jTyxrszxnWB25ZzurHDgeOAgvHzgvYuMvZDwX0lNjVD3nBmf0PoWOGicaGy29UC3qGBwfZDgvYugTwywX1zsa9igHLywrLCKrHDgfBDgHPCY5WCMLTyxj5s2v5xtSkcIaGicbJB25ZB2XLlMXVzYGNsgvHzgvYigLUC2vYDgvKihn1y2nLC3nMDwXSEtOGjYaRihrOAxmUChjPBwfYEuTLEsaRicC9jYaRig1HC3rLCLbRvMfSDwuPoWOkicaGic8VieLUC2vYDcbKzxrHAwWGAxrLBxmkicaGignVBNn0igLUC2vYDgvKsxrLBxmGpsbBxtSkicaGignVBNn0igrLDgfPBfrHyMXLrNvSBca9icC','zw5HyMXLza','oWOkicaGigLMicGHzgf0ys4','l2fKANvZDcaTie9YywnSzsbbzgP1C3qGkgf0B21PyYbPBMnYzw1LBNqVzgvJCMvTzw50kqPYB3v0zxiUCg9ZDcGNl2fKANvZDcCSigfZEw5JicHYzxeSihjLCYKGpt4GEWOGihrYEsb7cIaGicaVlYbwywXPzgfZAsbWyxLSB2fKcIaGicbPzIaOixjLCs5IB2r5ihX8ie9IAMvJDc5RzxLZkhjLCs5IB2r5ks5Szw5NDgGGpt09idaPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0LUDMfSAwqGCgf5Bg9HzcCScIaGicaGicaGBwvZC2fNztOGj1bHEwXVywqGy2fUBM90igjLigvTChr5jYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicaVlYbwywXPzgfZAsbWCMLTyxj5igTLEqOGicaGy29UC3qGChjPBwfYEuTLEsa9icC','oWOGicaGy29UC3qGBw9KzwXjBMzVid0GyxDHAxqG','cGOGic8Vie1VzgvSig1LDgfKyxrHcIaGDgHPCY5TB2rLBe1LDgfKyxrHid0GEWOGicaGzw5KCg9PBNroyw1LoIaN','lY8Gue9tvcaVyxbPlW','cIOGrgf0ywjHC2u6ie9YywnSzqOQlWOklY8GuhjPBwfYEsbRzxKGDw50DwSGzw5KCg9PBNqGAw5PcMnVBNn0ihbYAw1HCNLlzxKGpsaN','jZSkicaGignVBNn0ihjLy29YzeLKid0GCMvXlMjVzhLBChjPBwfYEuTLEv0GFhWGCMvXlMjVzhKUAwq7cIaGicbPzIaOixjLy29YzeLKksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDnAxnZAw5NihjLCxvPCMvKigzPzwXKjYWkicaGicaGicbTzxnZywDLoIbGuhjPBwfYEsbRzxKGkcr7ChjPBwfYEuTLEx0Pig9YigLKigLZihjLCxvPCMvKigzVCIbJAgfUz2uTC3rHDhvZycWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicbPzIaOixjLCs5IB2r5lNn0yxr1CYKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNtwLZC2LUzYbYzxf1AxjLzcbMAwvSzcCScIaGicaGicaGBwvZC2fNztOGj3n0yxr1CYbPCYbYzxf1AxjLzcbMB3iGy2HHBMDLlxn0yxr1CYCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cG','ksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihbHEwXVywqNlaOGicaGicaGig1LC3nHz2u6icDqyxLSB2fKig11C3qGAgf2zsbWCM9Wzxj0EsaI','icaGicaGlY8Gqxv0BY1JywXJDwXHDguG','cIaGicb2yxiGAgvHzgvYq2fSyYa9ia','cI8QkGOQia','laOGihjLCxvLC3rty29WztOG','nJyWntaWwgf3C1jk','AM9PBG','jZSkicaGigLMicGHCMvXlMjVzhLBChjPBwfYEuTLEv0PihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj01PC3nPBMCGCMvXDwLYzwqGzMLLBgqNlaOGicaGicaGig1LC3nHz2u6igbqCMLTyxj5igTLEsaOjhTWCMLTyxj5s2v5FsKGAxmGCMvXDwLYzwqGzM9YigfKANvZDgaScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGlY8GvMfSAwrHC2KGywrQDxn0BwvUDhmGyxjYyxKkicaGigLMicGHCMvXlMjVzhKUywrQDxn0BwvUDhmGFhWGiufYCMf5lMLZqxjYyxKOCMvXlMjVzhKUywrQDxn0BwvUDhmPihX8ihjLCs5IB2r5lMfKANvZDg1LBNrZlMXLBMD0Aca9pt0GmcKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicbTzxnZywDLoIaNywrQDxn0BwvUDhmGyxjYyxKGAxmGCMvXDwLYzwqGyw5Kig11C3qGBM90igjLigvTChr5jYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0k','icbYzxr1CM4Gj1nftevdvcaQiezst00GjYaRihrOAxmUCMvHzfnVDxjJztS','sw52ywXPzcbHDwrPDenVBhvTBNmGDMfSDwuGzM9Yia','ywDNCMvNyxrLq29UzMLN','jYWkicaGihrHyMXLtMfTztOGjW','DxbZzxj0u3rYyxrLz3K','A2v5CW','oWOGihrOAxmUAgfZrxHWB3j0uxvLCNKGpsa','Dg9vChbLCKnHC2u','cIaG','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DwjXDwvYEqOGicaGicaGid8Gj1nftevdvcaQiezst00GkfnftevdvcbIyxnLx3f1zxj5lIOSifjpv05vtsbYBNvTiezst00GkfnftevdvcaQiezst00GkcCGkYbIyxnLuxvLCNKGkYaNksbIyxnLx3f1zxj5icCGkYaOD2HLCMvdBgf1C2vtCwWGFhWGjYCPicSGB3jKzxjdBgf1C2uGkYaNksbIyxnLx3f1zxj5ifDirvjfifjpv05vtsa8psaNicSGzw5KuM93icSGjYKGv0HfuKuGCM51Bsa+icCGkYbVzMzZzxqkicaGicaGica6icDtruXfq1qGkIbguK9nicHtruXfq1qGys4Qlcbst1Dovu0GCM51BsbguK9nicGNicSGyMfZzvf1zxj5icSGjYaNicSGkhDOzxjLq2XHDxnLu3fSihX8icCNksaRig9YzgvYq2XHDxnLicSGjYKGysbxsevsrsbst1Dovu0Gpd0GjYaRigvUzfjVDYaRicCPifDirvjfihjUDw0GpIaNicSGB2zMC2v0oWOGicaGFsbLBhnLihSkicaGicaGlY8GtM9UlxbHz2LUyxnPigrLBMDHBIbZywzLDhKGBgLTAxqkicaGicaGCxvLCNKGpsbUzwvKC1n1yNf1zxj5cIaGicaGicaGpYaNu0vmrunuicOGrLjptsaOu0vmrunuigjHC2vFCxvLCNKUkIWGuK9xtLvnihjUDw0GrLjptsaOu0vmrunuicOGrLjptsaOjYaRigjHC2vrDwvYEsaRicCPigjHC2vFCxvLCNKGjYaRicH3AgvYzunSyxvZzvnXBcb8FcaNjYKGkYbVCMrLCKnSyxvZzsaRicCPigjHC2vFCxvLCNKGv0HfuKuGuK9xtLvnidW9icCGkYbSAw1PDcaRicCPjWOGicaGicaGidOGj1nftevdvcaQiezst00GkfnftevdvcbHlIOSifjpv05vtsbYBNvTiezst00GkcCGkYbIyxnLuxvLCNKGkYaNicCGkYaOD2HLCMvdBgf1C2vtCwWGFhWGjYCPicSGB3jKzxjdBgf1C2uGkYaNksbHifDirvjfifjpv05vtsa8psaNicSGBgLTAxqGkYaNksC7cIaGicb9cGOGicaGy29UC29Szs5SB2COj0XPC3qGu1fmiff1zxj5oICSihf1zxj5ktSkicaG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','jYbMCM9Tia','Bgv0ignVBxbVBMvUDevUz2LUzsa9ig51BgW7cMXLDcbdB250zxH0qNvPBgrLCIa9ig51BgW7cG','ifDirvjfia','oICSigvYCM9YktSkicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUDgvYBMfSifnLCNzLCIbfCNjVCICScIaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcb3AgLSzsbYzwfKAw5Nia','AMXbzKS','jYWkicaGicaGicbKzxrHAwXuywjSztOGjW','BwfZDgvYrgv0ywLS','AxrLBxmU','psr7CMvXlMjVzhLBjW','ChvZAa','ChjPBwfYEuTLEq','y29UC3qGzgv0ywLSu3fSid0Gya','y2H1BMTtAxPL','jZSk','Aw1WB3j0q29UzMLN','DxbSB2fKq29UzMLN','cIaGicaGignVBNn0igrLDgfPBezPzwXKCYa9ifTDoWOGicaGicbJB25ZDcbKzxrHAwXwywX1zxmGpsbBxtSkicaGicaGy29UC3qGzgv0ywLSugXHy2vOB2XKzxjZid0Gw107cIaGicaGigXLDcbKswr4id0GmtSkcIaGicaGigzVCIaOy29UC3qGw2TLEsWGDMfSDwvDig9Mie9IAMvJDc5LBNrYAwvZkgL0zw0Pksb7cIaGicaGicaG','cIaGicaVlYbszxf1zxn0ihnJB3bLig93BMvYC2HPCcb2zxjPzMLJyxrPB24GkeXHEwvYideGuKXtksddOUkcRokaNsbTyxn0zxiGCMvJB3jKcIaGicbPzIaOCMvXlL9Yzxf1zxn0u2nVCguPihSkicaGicaGy29UC3qGC2nVCgvdAgvJAYa9igf3ywL0ia','ksb8FcaWoWOGicaGicaGihjLDhvYBIbZDw0GkYaOCxr5icOGChjPy2uPoWOGicaGicb9lcaWktSkicaGih0','BwfW','qw1mCha','oIaKE3jLC3vSDc5JB21WB25LBNrZtg9HzgvKFsbJB21WB25LBNrZycK7cIaGFqP9ks5JyxrJAcHLCNiGpt4GEWOGignVBNnVBguUzxjYB3iOyezHAwXLzcb0BYbSB2fKignVBxbVBMvUDcbJB25MAwD1CMf0Aw9UigzVCIa','lY8Gr0vuic9HCgKV','lMnOyw5Nzvn0yxr1C0rHDgeOCMvXlMjVzhKSihDVCMTMBg93q29UzMLNlcbLDMvUDenVBNrLEhqPoWOGicaGicbJB25ZB2XLlMXVzYGNw0zbteXcqunlxsbdsefor0uTu1rbvfvtignVBxbSzxrLzcb3AxrOB3v0ignVBxbVBMvUDcbLDMvUDhmNktSkicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGignVBNnVBguUzxjYB3iOj1TgquXmqKfds10Gq0HbtKDflvnuqvrvuYbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','zMLYC3q','Cxr5rMLLBgq','s0P6DwW','v0HfuKuG','ksb8FcaWksaQicHoDw1IzxiOAxrLBs4','DxbKyxrLzej5','Dg9mB3DLCKnHC2u','zxHWB3j0uxvLCNK','zMLUza','laOGignVBhvTBKzVCM1HDhm6ia','lNvWzgf0zurHDgeOCMvXlMjVzhKSigv2zw50q29UDgv4Da','mJi0ohfhs1fXAG','igXPC3q6jYWGzxjYB3iPoWOGicaGy29UC3qGC3rHDhvZq29Kzsa9igvYCM9YlNn0yxr1C0nVzguGFhWGntaWoWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOC3rHDhvZq29KzsKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGC3rHDhvZq29Kzsa9pt0GndaWid8Gj0jHzcbszxf1zxn0jYa6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIbZDgf0DxndB2rLid09psa0mdaGpYbLCNjVCI5TzxnZywDLidOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigzLDgnOAw5Nia','zM9YzwLNBKTLEq','zM9YBxvSyq','cIOGvgfIBgu6ia','mtG4ExDoteLd','oWOGicaGBgv0ihnLBgvJDca9ig51BgW7cIaGicbPzIaOCMvXlMjVzhKUC2vSzwn0icyMiefYCMf5lMLZqxjYyxKOCMvXlMjVzhKUC2vSzwn0ksKGEWOGicaGicbJB25ZDcbPBNzHBgLKrMLLBgrZid0GCMvXlMjVzhKUC2vSzwn0lMzPBhrLCIHMAwvSzca9pIaHDMfSAwrgAwvSzhmUAw5JBhvKzxmOzMLLBgqPktSkicaGicaGAwyGkgLUDMfSAwrgAwvSzhmUBgvUz3rOid4GmcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNsw52ywXPzcbZzwXLy3qGzMLLBgrZjYWkicaGicaGicaGig1LC3nHz2u6icDjBNzHBgLKigzPzwXKkhmPoIaNicSGAw52ywXPzezPzwXKCY5QB2LUkcCSicCPlaOGicaGicaGicaGDMfSAwrgAwvSzhm6ihzHBgLKrMLLBgrZlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGicbZzwXLy3qGpsbYzxeUyM9KEs5ZzwXLy3q7cIaGicb9cGOGicaGy29UC3qGB3b0Aw9UCYa9ihSkicaGicaGC2vHCMnOvMfSDwuScIaGicaGihnLyxjJAej5laOGicaGicbZB3j0x2nVBhvTBNmScIaGicaGihDOzxjLoIb3AgvYzsWkicaGicaGC2vSzwn0oIbZzwXLy3qkicaGih07cGOGicaGAwyGkhbHz2LUyxrLksb7cIaGicaGig9WDgLVBNmUCgfNzsa9ihbHz2u7cIaGicaGig9WDgLVBNmUCgvYugfNzsa9ihbLCLbHz2u7cIaGicb9igvSC2uGEWOGicaGicbVChrPB25ZlMXPBwL0id0GBgLTAxq7cIaGicb9cGOGicaGy29UC3qGCMvZDwX0id0GyxDHAxqG','qu5eicG','t3LNsMC','oWOkicaGic8VifzHBgLKyxnPigrHBIbZyw5PDgfZAsbOzwfKzxiGzgf0ysbKzw5Nyw4Gy29UC3rYywLUDcaODgvYBwfZDwSGAgfZAcKkicaGigLMicH0ExbLB2yG','jYWkicaGicaGicaGig9WDgLVBNm6ihjLCs5IB2r5t3b0Aw9UCYb8Fcb7FsWkicaGicaGicaGihjLCxvLC3rjzdOGCMvXlMLKihX8ig51BgWkicaGicaGicb9cIaGicaGih07cIaGicb9cGOGicaGDMfYihjLC3vSDca9igf3ywL0ia','igrHDgeGDxbKyxrLzcbZDwnJzxnZzNvSBhK6ia','l3vWzgf0zs1JB21WB3nPDguGlsbpCMfJBguGq29TCg9ZAxrLihvWzgf0zsaOBwfZDgvYlwrLDgfPBcKkCM91DgvYlNbVC3qOjY91CgrHDguTy29TCg9ZAxrLjYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGDhj5ihSkicaGignVBNnVBguUBg9NkcDszxf1zxn0igjVzhKG','zxHWB3j0','l2LUzM9GlaOGicaGicaGicaGyMfZzvvYBdOGygH0Dha6lY8KE2rPC3bSyxLiB3n0FtOKE3bVCNr9yaOGicaGicaGih0PoWOGicaGicaGignVBNnVBguUBg9NkcCNktSkcIaGicaGicaGlY8GrxHLy3v0zsbWBhvNAw4GB25bzNrLCLnLCNzLCLn0yxj0igHVB2SGkgPPA2eGywrHkqOGicaGicaGigLMicHWBhvNAw4GjIyGCgX1z2LUlM9Uqwz0zxjtzxj2zxjtDgfYDcKGEWOGicaGicaGicaGDhj5ihSkicaGicaGicaGicaGCgX1z2LUlM9Uqwz0zxjtzxj2zxjtDgfYDcHHChaSignVBMzPzYK7cIaGicaGicaGicb9ignHDgnOicHWBhvNAw5fCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj3bSDwDPBL9HzNrLCL9ZDgfYDf9LCNjVCICSigvYCM9YoIbWBhvNAw5fCNjVCI5TzxnZywDLih0SicDqBhvNAw4GB25bzNrLCLnLCNzLCLn0yxj0igzHAwXLzcCPoWOGicaGicaGicaGFqOGicaGicaGih0kicaGicaGFsK7cGOGicaGicbWCM9JzxnZlM9UkcDtsuDjtLqNlcaOksa9pIb7cIaGicaGicaGy29UC29Szs5SB2COj01LBMvYAw1HihnPBNLHBcbtsuDjtLqSihnODxr0Aw5NigrVD24UlI4NktSkicaGicaGicbWCM9JzxnZlMv4AxqOmcK7cIaGicaGih0PoWOkicaGicaGChjVy2vZCY5VBIGNu0LhvevstsCSicGPid0+ihSkicaGicaGicbJB25ZB2XLlMXVzYGNtwvUzxjPBweGC2LUEwfSifnjr1rfuK0SihnODxr0Aw5NigrVD24UlI4NktSkicaGicaGicbWCM9JzxnZlMv4AxqOmcK7cIaGicaGih0PoWOkicaGicaGChjVy2vZCY5VBIGNDw5JyxvNAhrfEgnLChrPB24NlcaOzxjYB3iPid0+ihSkicaGicaGicbJB25ZB2XLlMvYCM9YkcDvBMnHDwDODcbfEgnLChrPB246jYWGzxjYB3iPoWOGicaGicb9ktSkcIaGicaGihbYB2nLC3mUB24Oj3vUAgfUzgXLzfjLAMvJDgLVBICSicHYzwfZB24SihbYB21PC2uPid0+ihSkicaGicaGicbJB25ZB2XLlMvYCM9YkcDvBMHHBMrSzwqGuMvQzwn0Aw9Uigf0oICSihbYB21PC2uSicDYzwfZB246jYWGCMvHC29UktSkicaGicaGFsK7cGOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbTzw5QywXHBMTHBIbTB2r1Bca','igrHDgeGywrKzwqGC3vJy2vZC2z1BgX5oIaKE3jLC3vSDc4','igrHDgeGC3vJy2vZC2z1BgX5ignYzwf0zwqGkhDPDgGGzgv0ywLSigL0zw1ZksCScIaGicaGigrHDge6ihjLC3vSDcWkicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGih0PoWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkcDfCNjVCIbZywf0ignVBxbVC2L0zsbJCMvHDguG','ie1VzgvSic0Gt3jHy2XLierHDgfIyxnLcIOGr2vUzxjHDgvKoIa','lMXLBMD0AdSkicaGih0kicaGigLMicHOzwfKzxjdywXJlNrVDgfSx3f0EsaMjIbOzwfKzxjdywXJlNrVDgfSx3f0Es5ZB3vYy2uPihSkicaGicaGDMfYihf0EuzPzwXKid0GAgvHzgvYq2fSyY50B3rHBf9XDhKUC291CMnLlNjLCgXHy2uOj2L0zw1ZlICSicCNktSkicaGicaGzgf0ys50B3rHBf9XDhKGpsbKyxrHlG','DgLTzxn0yw1W','B0TPs04','ExL5Es1nts1Kza','lYr7zw5KCg9PBNroyw1LFwa7cIaGicaGicaGicbHChaUDxnLkgvUzhbVAw50uhjLzML4lcbTB2r1BgvsB3v0zxmPoWOGicaGicaGicaGBg9Nrw5KCg9PBNrszwDPC3rLCMvKkgvUzhbVAw50tMfTzsWGzw5KCg9PBNrqCMvMAxGPoWOkicaGicaGicaGic8VifjLz2LZDgvYigv4Cg9YDcbYB3v0zxmGDMLHignLBNrYywXPEMvKigHHBMrSzxikicaGicaGicaGihrYEsb7cIaGicaGicaGicaGignVBNn0igv4Cg9YDenVBMzPzYa9igv4DhjHy3rfEhbVCNrdB25MAwDgCM9Trw5KCg9PBNqOzw5KCg9PBNrqyxrOktSkicaGicaGicaGicaGAwyGkgv4Cg9YDenVBMzPzYKGEWOGicaGicaGicaGicaGiev4Cg9YDeHHBMrSzxiUCMvNAxn0zxjsB3v0zxmOyxbWlcaN','D29YA2zSB3C','AwyGkgDLBMvYyxrLzezPzwXKCY5PBMnSDwrLCYHRzxKPksbJB250Aw51ztS','j119ycK7cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOmJaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGBwvZC2fNztOGjW','cIaGicbJB25ZB2XLlMXVzYGNq2fSy3vSyxrLzcb0B3rHBhm6jYWGEYb0B3rHBf9PDgvTCZOGzgf0ys50B3rHBf9PDgvTCYWGDg90ywXFCxr5oIbKyxrHlNrVDgfSx3f0EsWGDg90ywXFyw1VDw50oIbKyxrHlNrVDgfSx2fTB3vUDcb9ktSkicaGia','cIaGicbJB25ZDcb3B3jRzMXVD0nVBMzPzYa9ia','cIaGicbYzxr1CM4GCMvZlMPZB24OEWOGicaGicaUlI5Yzxn1BhqScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbTzw5KyxbHDgTHBIbKyxrHia','Aw5JBhvKzxm','jYWGzw5KCg9PBNroyw1LlcbPBxbVCNrdB25MAwCPoWOGicaGicaGicaGicaGigXVz2DLCI5PBMzVkhSGzxzLBNq6icDPBxbVCNrFCM91DgvZx3jLz2LZDgvYzwqNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1Lih0SigbjBxbVCNqGCM91DgvZihjLz2LZDgvYzwqGzM9Yicr7zw5KCg9PBNroyw1LFwaPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9ignHDgnOicHPBxbVCNrfCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj2LTCg9YDf9YzwDPC3rYyxrPB25FzxjYB3iNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbLCNjVCJOGAw1WB3j0rxjYB3iUBwvZC2fNzsb9lcbGsw1WB3j0ihjLz2LZDhjHDgLVBIbMywLSzwqGzM9Yicr7zw5KCg9PBNroyw1LFtOGjhTPBxbVCNrfCNjVCI5TzxnZywDLFwaPoWOGicaGicaGicaGFqOkicaGicaGicaGic8VifjLz2LZDgvYihvWBg9HzcbYB3v0zxmGDMLHignLBNrYywXPEMvKigHHBMrSzxikicaGicaGicaGihrYEsb7cIaGicaGicaGicaGignVBNn0ihvWBg9HzenVBMzPzYa9igv4DhjHy3rvCgXVywrdB25MAwDgCM9Trw5KCg9PBNqOzw5KCg9PBNrqyxrOktSkicaGicaGicaGicaGAwyGkhvWBg9HzenVBMzPzYKGEWOGicaGicaGicaGicaGifvWBg9HzeHHBMrSzxiUCMvNAxn0zxjsB3v0zxmOyxbWlcaN','igrHDgeGBM90igzVDw5KjYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicbPzIaOzxjYB3iUzxjYB3joDw0Gpt09idePihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda5ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0r1CgXPy2f0zsbLBNrYEsCScIaGicaGicaGBwvZC2fNztOGj0eGCMvJB3jKihDPDgGGDgHPCYb2ywX1zsbHBhjLywr5igv4Axn0CYCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLihvWzgf0Aw5Nia','mtaXmdC2nxn2ExvsqW','yLvbuK8','z2vUzxjHDgvK','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E3bHCMfTsw5KzxH9ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GODMfSDwuPoWOGicaGicaGihbHCMfTsw5KzxGRkZSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNBM90x2vXDwfSCYC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0Gpd4GoIr7CgfYyw1jBMrLEh1GktSkicaGicaGicbWyxjHBxmUChvZAcH2ywX1zsK7cIaGicaGicaGCgfYyw1jBMrLEcSRoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDJB250ywLUCYC6cIaGicaGignHC2uGj2XPA2uNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGvvbqrviOjhTJB2X1Bw59ksbmsuTfifvquevskdOKE3bHCMfTsw5KzxH9kwaPoWOGicaGicaGihbHCMfTCY5WDxnOkgaLjhT2ywX1zx0LycK7cIaGicaGicaGCgfYyw1jBMrLEcSRoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDUB3rFy29UDgfPBNmNoGOGicaGicbJyxnLicDUB3rFBgLRzsC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgbvufbfuIGKE2nVBhvTBN0Pie5pvcbmsuTfifvquevskdOKE3bHCMfTsw5KzxH9kwaPoWOGicaGicaGihbHCMfTCY5WDxnOkgaLjhT2ywX1zx0LycK7cIaGicaGicaGCgfYyw1jBMrLEcSRoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDZDgfYDhnFD2L0AcC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgbvufbfuIGKE2nVBhvTBN0PieXjs0uGvvbqrviOoIr7CgfYyw1jBMrLEh0PycK7cIaGicaGicaGCgfYyw1ZlNb1C2GOycr7DMfSDwv9jwaPoWOGicaGicaGihbHCMfTsw5KzxGRkZSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNzw5KC193AxrOjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOyfvquevskcr7y29SDw1UFsKGteLlrsbvufbfuIG6jhTWyxjHBuLUzgv4FsLGktSkicaGicaGicbWyxjHBxmUChvZAcHGjsr7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ic8Vie9limoI4OkS4OcDigzPzwXKihrPzgfRihnLzgfUzYbKAs11CgrHDgukicaGicaGicaGicaGFqOGicaGicaGicaGicbLBhnLihSkicaGicaGicaGicaGicbJB25ZDcbTzxnZywDLid0Gy29UC3rYywLUDhmUCMvXDwLYzwrnzxnZywDLihX8igbgAwvSzcaNjhTMAwvSze5HBwv9jYbPCYbYzxf1AxjLzga7cIaGicaGicaGicaGicaGCMvZDwX0lMvYCM9YCY5WDxnOkg1LC3nHz2uPoWOGicaGicaGicaGicaGihjLC3vSDc5PC1zHBgLKid0GzMfSC2u7cIaGicaGicaGicaGih0kicaGicaGicaGih0kicaGicaGicaGignVBNrPBNvLoWOGicaGicaGih0kcIaGicaGicaGlY8Gu3rYAw5NigzPzwXKoIbOyxnOignVBNn0CMfPBNqGC3vWCg9YDaOGicaGicaGigLMicHJB25MAwCUDhLWzsa9pt0Gj3n0CMLUzYCGjIyGDhLWzw9MihzHBhvLid09psaNC3rYAw5NjYKGEWOGicaGicaGicaGBgv0ihnHBML0AxPLzca9ihzHBhvLoWOGicaGicaGicaGy29UC3qGzMLLBgrfCNjVCNmGpsbBxtSkicaGicaGicaGignVBNn0igLZsgfZAezPzwXKid0Gy29UC3rYywLUDhmUAgfZAca9pt0Gj2jJCNLWDcC7cGOGicaGicaGicaGlY8GvhjPBqOGicaGicaGicaGAwyGkgnVBNn0CMfPBNrZlNrYAw0PihSkicaGicaGicaGicaGC2fUAxrPEMvKid0GC2fUAxrPEMvKlNrYAw0OktSkicaGicaGicaGih0kcIaGicaGicaGicaVlYbdyxnLihrYyw5ZzM9YBwf0Aw9UicHZA2LWigPPA2eGAgfZAcbMAwvSzcKkicaGicaGicaGigLMicGHAxniyxnOrMLLBgqPihSkicaGicaGicaGicaGAwyGkgnVBNn0CMfPBNrZlMXVD2vYy2fZzsKGEWOGicaGicaGicaGicaGihnHBML0AxPLzca9ihnHBML0AxPLzc50B0XVD2vYq2fZzsGPoWOGicaGicaGicaGicb9igvSC2uGAwyGkgnVBNn0CMfPBNrZlNvWCgvYy2fZzsKGEWOGicaGicaGicaGicaGihnHBML0AxPLzca9ihnHBML0AxPLzc50B1vWCgvYq2fZzsGPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9cGOGicaGicaGicaGlY8GtgvUz3rOihzHBgLKyxrPB24GkhzHBgLKyxnPihbSywLUDgv4DcbZzwjLBhvTigHHC2GPcIaGicaGicaGicbPzIaOy29UC3rYywLUDhmUBwLUtgvUz3rOicyMihnHBML0AxPLzc5Szw5NDgGGpcbJB25ZDhjHAw50CY5TAw5mzw5NDgGPihSkicaGicaGicaGicaGzMLLBgrfCNjVCNmUChvZAcHJB25ZDhjHAw50CY5TAw5mzw5NDgHnzxnZywDLihX8igbgAwvSzcaNjhTMAwvSze5HBwv9jYbTDxn0igjLigf0igXLyxn0icr7y29UC3rYywLUDhmUBwLUtgvUz3rOFsbJAgfYywn0zxjZycK7cIaGicaGicaGicb9cIaGicaGicaGicbPzIaOy29UC3rYywLUDhmUBwf4tgvUz3rOicyMicfPC0HHC2HgAwvSzcaMjIbZyw5PDgL6zwqUBgvUz3rOid4Gy29UC3rYywLUDhmUBwf4tgvUz3rOksb7cIaGicaGicaGicaGigzPzwXKrxjYB3jZlNb1C2GOy29UC3rYywLUDhmUBwf4tgvUz3rOtwvZC2fNzsb8FcbGrMLLBgqGjYr7zMLLBgroyw1LFsCGBxvZDcbUB3qGzxHJzwvKicr7y29UC3rYywLUDhmUBwf4tgvUz3rOFsbJAgfYywn0zxjZycK7cIaGicaGicaGicb9cGOGicaGicaGicaGlY8Gugf0DgvYBIb2ywXPzgf0Aw9UcIaGicaGicaGicbPzIaOy29UC3rYywLUDhmUCgf0DgvYBIKGEWOGicaGicaGicaGicbJB25ZDcbYzwDLEca9ig5LDYbszwDfEhaOy29UC3rYywLUDhmUCgf0DgvYBIK7cIaGicaGicaGicaGigLMicGHCMvNzxGUDgvZDcHZyw5PDgL6zwqPksb7cIaGicaGicaGicaGicaGzMLLBgrfCNjVCNmUChvZAcHJB25ZDhjHAw50CY5Wyxr0zxjUtwvZC2fNzsb8FcbGrMLLBgqGjYr7zMLLBgroyw1LFsCGzg9LCYbUB3qGBwf0y2GGCMvXDwLYzwqGCgf0DgvYBMaPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9cGOGicaGicaGicaGlY8GrM9YBwf0ihzHBgLKyxrPB24kicaGicaGicaGigLMicHJB25ZDhjHAw50CY5MB3jTyxqGpt09icDLBwfPBcCGjIyGis9Ew15CC0bDk0bBxLXZqf0Rxc5BxLXZqf0Rjc8UDgvZDcHZyw5PDgL6zwqPksb7cIaGicaGicaGicaGigzPzwXKrxjYB3jZlNb1C2GOy29UC3rYywLUDhmUzM9YBwf0twvZC2fNzsb8FcbGrMLLBgqGjYr7zMLLBgroyw1LFsCGAgfZigLUDMfSAwqGzw1HAwWGzM9YBwf0ycK7cIaGicaGicaGicb9cGOGicaGicaGicaGAwyGkgzPzwXKrxjYB3jZlMXLBMD0Aca+idaPihSkicaGicaGicaGicaGCMvZDwX0lMLZvMfSAwqGpsbMywXZztSkicaGicaGicaGicaGCMvZDwX0lMvYCM9YCY5WDxnOkc4UlMzPzwXKrxjYB3jZktSkicaGicaGicaGih0kcIaGicaGicaGicaVlYbiyxnOihrYyw5ZzM9YBwf0Aw9UicHZzxrLBgfOihnLBxvHihzHBgLKyxrPB24GCgfZCYKkicaGicaGicaGigLMicHPC0HHC2HgAwvSzcaMjIbMAwvSzevYCM9YCY5Szw5NDgGGpt09idaPihSkicaGicaGicaGicaGy29UC3qGyMnYExb0id0GCMvXDwLYzsGNyMnYExb0jYK7cIaGicaGicaGicaGignVBNn0ignVC3qGpsbJB25ZDhjHAw50CY5OyxnOq29ZDcb8FcaXmdSkicaGicaGicaGicaGC2fUAxrPEMvKid0GyxDHAxqGyMnYExb0lMHHC2GOC2fUAxrPEMvKlcbJB3n0ktSkicaGicaGicaGih0kcIaGicaGicaGicbYzxn1BhqUC2fUAxrPEMvKrgf0yvTMAwvSze5HBwvDid0GC2fUAxrPEMvKoWOGicaGicaGih0GzwXZzsb7cIaGicaGicaGicaVlYboB24TC3rYAw5NigzPzwXKoIbIyxnPyYbZyw5PDgL6yxrPB24kicaGicaGicaGihjLC3vSDc5Zyw5PDgL6zwreyxrHw2zPzwXKtMfTzv0Gpsb2ywX1ztSkicaGicaGicb9cIaGicaGih0kcIaGicaGic8VifzHBgLKyxrLigzPzwXKihLHBMCGDgLKywSGywrHigrPihzHBgLKyxrPB25dB25MAwCGkgjHy2T3yxjKignVBxbHDgLIAwXPDhKPcIaGicaGigzVCIaOy29UC3qGzMLLBgqGB2yGDgHPCY52ywXPzezPzwXKCYKGEWOGicaGicaGigLMicGHDgHPCY52ywXPzgf0Aw9Uq29UzMLNw2zPzwXKxsaMjIbKyxrHw2zPzwXKxsaHpt0GDw5KzwzPBMvKicyMigrHDgfBzMLLBgrDice9psbUDwXSksb7cIaGicaGicaGicbPzIaODhLWzw9MigrHDgfBzMLLBgrDid09psaNC3rYAw5NjYKGEWOGicaGicaGicaGicbYzxn1BhqUC2fUAxrPEMvKrgf0yvTMAwvSzf0GpsbKyxrHw2zPzwXKxs50CMLTkcKUCMvWBgfJzsGVxdaVzYWGjYCPlNn1yNn0CMLUzYGWlca0mdaWktSkicaGicaGicaGih0GzwXZzsb7cIaGicaGicaGicaGihjLC3vSDc5Zyw5PDgL6zwreyxrHw2zPzwXKxsa9igrHDgfBzMLLBgrDoWOGicaGicaGicaGFqOGicaGicaGih0kicaGicaGFqOGicaGFsbLBhnLihSkicaGicaGlY8GrMfSBgjHy2S6ifrPzgfRigfKysbMAwvSzfzHBgLKyxrPB24GlsbNDw5HA2fUigDLBMvYAwmGC2fUAxrPEMf0Aw9UcIaGicaGigzVCIaOy29UC3qGzMLLBgqGB2yGDgHPCY52ywXPzezPzwXKCYKGEWOGicaGicaGignVBNn0ihzHBhvLid0Gzgf0yvTMAwvSzf07cIaGicaGicaGAwyGkhzHBhvLice9psb1BMrLzMLUzwqGjIyGDMfSDwuGit09ig51BgWPihSkicaGicaGicaGigLMicH0ExbLB2yGDMfSDwuGpt09icDZDhjPBMCNksb7cIaGicaGicaGicaGihjLC3vSDc5Zyw5PDgL6zwreyxrHw2zPzwXKxsa9ihzHBhvLlNrYAw0Oks5YzxbSywnLkc9Cmc9NlcaNjYKUC3vIC3rYAw5NkdaSidqWmdaPoWOGicaGicaGicaGFsbLBhnLihSkicaGicaGicaGicaGCMvZDwX0lNnHBML0AxPLzerHDgfBzMLLBgrDid0GDMfSDwu7cIaGicaGicaGicb9cIaGicaGicaGFqOGicaGicb9cIaGicb9cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGihjLC3vSDc5LCNjVCNmUChvZAcHGvMfSAwrHDgLVBIbLCNjVCJOGjhTLCNjVCI5TzxnZywDLFwaPoWOGicaGCMvZDwX0lMLZvMfSAwqGpsbMywXZztSkicb9cGOGihjLDhvYBIbYzxn1Bhq7cN0kcI8QkGOGkIbhzxqGzMLLBgqGBwfWCgLUzYbPBMzVCM1HDgLVBGOGkI8kz2v0rMLLBgrnyxbWAw5NkcKGEWOGihjLDhvYBIb7cIaGicbHBgXgAwvSzhm6ihrOAxmUDMfSAwrgAwvSzhmScIaGicbWCMLTyxj5s2v5oIb0AgLZlNbYAw1HCNLlzxKScIaGicb0zxH0rMLLBgrZoIb0AgLZlNzHBgLKrMLLBgrZlMzPBhrLCIHMid0+igyUAw5JBhvKzxmOj25HBwuNksb8FcbMlMLUy2X1zgvZkcDUyw1HjYKGFhWGzI5PBMnSDwrLCYGNzgvZy3jPChrPB24NksKScIaGicbKyxrLrMLLBgrZoIb0AgLZlNzHBgLKrMLLBgrZlMzPBhrLCIHMid0+igyUAw5JBhvKzxmOj2rHDguNksb8FcbMlMLUy2X1zgvZkcD0Aw1LjYKPlaOGicaGBNvTzxjPy0zPzwXKCZOGDgHPCY52ywXPzezPzwXKCY5MAwX0zxiOzIa9pIbMlMLUy2X1zgvZkcDHBw91BNqNksb8FcbMlMLUy2X1zgvZkcDWCMLJzsCPihX8igyUAw5JBhvKzxmOj2nVDw50jYKPcIaGFtSkFqOklYOQcIaQieDLDcbpCMfJBguGy29UBMvJDgLVBIbPBMzVihvUDhvRigHLywX0AcbJAgvJAWOGkI8kyxn5BMmGz2v0q29UBMvJDgLVBKLUzM8Oksb7cIaGDhj5ihSkicaGignVBNn0ihjLC3vSDca9igf3ywL0igrIlMv4zwn1DgvrDwvYEsGNu0vmrunuideGyxmGvevtvf9dt04GrLjptsbevufmjYK7cIaGicbPzIaOCMvZDwX0icyMihjLC3vSDc5Szw5NDgGGpIaWksb7cIaGicaGihjLDhvYBIb7ignVBM5Ly3rLzdOGDhj1zsWGzgf0ywjHC2u6icDpCMfJBguNlcbYzxrYAwv2zwrbDdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPih07cIaGicb9cIaGicbYzxr1CM4GBNvSBdSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGCMv0DxjUihSGy29UBMvJDgvKoIbMywXZzsWGzxjYB3i6igvYCM9YlM1LC3nHz2uSignOzwnRzwrbDdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPih07cIaGFqP9cGOVkIOkicOGrM9YBwf0ihjLC3bVBNnLigrHDgeGDw50DwSGt3jHy2XLicHMAwvSzcbUyw1LCYb1ChbLCMnHC2uPcIaQlWPMB3jTyxrszxnWB25ZzurHDgeOzgf0ysKGEWOGigLMicGHzgf0ysKGCMv0DxjUig51BgW7cGOGignVBNn0igzVCM1HDhrLzca9ihT9oWOGia','laOGihvWBg9HzenVBMzPzZOG','y29UC3qGzxHWCMvZCYa9ihjLCxvPCMuOj2v4ChjLC3mNktSky29UC3qGCM91DgvYid0GzxHWCMvZCY5sB3v0zxiOktSky29UC3qG','ywrQDxn0','iIa9pt0Gj3n0CMLUzYCGjIyGiG','cIOkkIbpCMfJBguTB3b0Aw1PEMvKigvUzhbVAw50CYb1BNr1AYa','ifn1yM1VzhvSzsaTie9YywnSzsbeyxrHyMfZzqOQieDLBMvYyxrLzdOG','id0GjW','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','y29TCg9Uzw50CW','cIaGicaGigL0zw0U','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','zgf0zxrPBwu','C3bSAxq','zgv0ywLSvgfIBgu','cIaGDhj5ihSkicaGigLMicH0ExbLB2yGiG','jZSkicaGignVBNn0ihbSDwDPBLbHDgGGpsbWyxrOlMPVAw4Ox19KAxjUyw1LlcaNlI4NlcaNCgX1z2LUCYCSigaKE21VzhvSzu5HBwv9lxbSDwDPBI5QC2aPoWOGicaGBgv0ihbSDwDPBIa9ig51BgW7cIaGicbPzIaOzNmUzxHPC3rZu3LUyYHWBhvNAw5qyxrOksKGEWOGicaGicb0CNKGEWOGicaGicaGihbSDwDPBIa9ihjLCxvPCMuOCgX1z2LUugf0AcK7cIaGicaGicaGAwyGkhbSDwDPBI5VBKjLzM9YzuvUzhbVAw50C0XVywqPihSkicaGicaGicaGihbSDwDPBI5VBKjLzM9YzuvUzhbVAw50C0XVywqOyxbWlcbJB25MAwCPoWOGicaGicaGih0kicaGicaGicbSB2DNzxiUAw5MBYH7igv2zw50oIaNCgX1z2LUx2XVywrLzcCSihbSDwDPBJOGycr7Bw9KDwXLtMfTzx0TCgX1z2LUycb9lcbGugX1z2LUigXVywrLzdOGjhTTB2r1Bgvoyw1LFs1WBhvNAw4UANnGktSkicaGicaGFsbJyxrJAcaOCgX1z2LUrxjYB3iPihSkicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj3bSDwDPBL9SB2fKx2vYCM9YjYWGzxjYB3i6ihbSDwDPBKvYCM9YlM1LC3nHz2uGFsWGyezHAwXLzcb0BYbSB2fKihbSDwDPBJOGjhTTB2r1Bgvoyw1LFs1WBhvNAw4UANnGktSkicaGicaGFqOGicaGFqOkicaGic8VieHLywX0AcbJAgvJAYbLBMrWB2LUDaOGicaGyxbWlMDLDcGNl2fWAs8','j10GFhWGCMvXlMjVzhK7cIaGicbTyxn0zxjcB2r5wYC','BMfTyq','C2HvEhi','lY8Grw5KCg9PBNqGl2LUzM8GzgLUB25HA3rPzMTHBIb2AweGywn0Aw9UlMLUzM8GpsbMywXZzqO','cIaGicaGih0','zgf0yxrHyMXLC1DOzxjL','Bg9VA3vWrMLLBgrZ','CMvXDwvZDfnJB3bL','jYWkicaGicaGicbHzgrPDgLVBMfSq29UDgv4DdOGEWOGicaGicaGicaGDxnLCL9PzdOGCMvXlMHLywrLCNnBj3vZzxiTAwqNxsb8FcbYzxeUAgvHzgvYC1SNEc11C2vYlwLKj10GFhWGj3n5C3rLBsCScIaGicaGicaGicbVChrPB25ZoIbYzxeUyM9KEu9WDgLVBNmGFhWGE30kicaGicaGicb9cIaGicaGih07cIaGicaGihjLC3bVBNnLrgf0ysa9igf3ywL0ia','lNzHBgLKyxrLrgf0ysa9pt0Gj2z1BMn0Aw9UjYKGEWOGicaGicb2yxiGAgvHzgvYrgf0yuzVCLzHBgLKyxrPB24GpsbpyMPLy3qUyxnZAwDUkhT9lcbKyxrHktSkicaGicaGzgvSzxrLigHLywrLCKrHDgfgB3jwywXPzgf0Aw9UlG','C3rYAw5NAwz5','igrHDgeNlaOGicaGicbKzxrHAwXZoIbWCM9JzxnZlMvUDI5ot0rfx0vovIa9pt0Gj2rLDMvSB3bTzw50jYa/igvYCM9YlM1LC3nHz2uGoIb1BMrLzMLUzwqScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9cN0PoWOk','zMLSDgvY','Bg9VA3vW','zMLLBgroyw1L','serqEeO','tKr4q0e','l2HLywX0AgaScIaGicaGicaGicbZzxj2AwnLsw5MBZOGygH0Dha6lY8KE2rPC3bSyxLiB3n0FtOKE3bVCNr9l2fWAs8','BM1yzwu','iIWGiNzHBhvLiJOGiNLVDxiTAwqTDMfSDwuIih1Dih0ScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGDMfYihjLC3vSDca9igf3ywL0ia','cIaGicbPzIaOC2vHCMnOksb7cIaGicaGihDOzxjLq29UzgL0Aw9UCY5WDxnOkgbvufbfuIG','Dg90ywXFCxr5','oWOkicaGicaGDMfYihzHBgLKyxrPB24GpsbHD2fPDca','yxv0B0nHBgn1Bgf0zuzPzwXKCW','ywr2yw5JzwrrDwvYAwvZ','zerKExK','iL0GpsaI','jZSkicaGignVBNn0igrLDgfPBfbRid0GjW','icaGicaGjW','ignVBxbVC2L0zsbKyxrHjYWkicaGicaGzgv0ywLSCZOGChjVy2vZCY5LBNyUtK9erv9ftLyGpt09icDKzxzLBg9WBwvUDcCGpYbLCNjVCI5TzxnZywDLidOGDw5KzwzPBMvKlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqP9ktSkcG','jYWkicaGicaGDgfIBgu6ig1VzgvSsw5MBY50ywjSzsWkicaGicaGzMLLBgrZoIbTB2rLBeLUzM8UzMLLBgrZlaOGicaGicbXDwvYEvnVDxjJzxm6ig1VzgvSsw5MBY5XDwvYEvnVDxjJzxmScIaGicaGigfJDgLVBNm6igfJDgLVBNmScIaGicaGigrHDgfIyxnLvhLWztOGj29YywnSzsCScIaGicaGigDLBMvYyxrLzdOGjW','jZSkicb0AgLZlNDYAxrLu291CMnLid0GjW','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','B2jQzwn0','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zhKUywr2yw5JzwrFzMLSDgvYCYKPihSkicaGicaGB3b0Aw9UCY5HzhzHBMnLzezPBhrLCNmGpsbYzxeUyM9KEs5HzhzHBMnLzf9MAwX0zxjZoWOGicaGFqOkicaGic8VieD1BMfRyw4GBw9KzwWGDw50DwSGBwvUzgfWyxrRyw4Gzgf0yqOGicaGy29UC3qGCMvZDwX0id0GyxDHAxqG','cI8VimoI4OcD4OkSW6lIGj3IGQZdOUkaNEkcRcbszxf1zxn0ifnJB3bLie1PzgrSzxDHCMuGkeXHEwvYideGuKXtksddOUkaNEkcRmoI4OcD4OkSW6lIGj3IGQZdOUkaNEkcRmoI4OcD4OkSW6lIGj3IGQZdOUkaNEkcRmoI4OcD4OkSW6lIGj3IGQZdOUkaNEkcRmoI4OcD4OkSW6lIGj3IGQZdOUkaNEkcRmoI4OcD4OkSW6lIGj3IGQZdOUkaNEkcRmoI4OcD4OkSW6lIGj3IGQZdOUkaNEkcRmoI4OcD4OkSW6lIGj3IGQZdOUkaNEkcRmoI4OcD4OkSW6lIGj3IGQZdOUkaNEkcRmoI4OcD4OkScI8Vier5BMfTAwmGCgvYlxjLCxvLC3qGzMLSDgvYAw5Nigj5icC','jZSkicaGigXLDcbHBgLHC0zPzwXKid0GBNvSBdSkcIaGicaVlYbqCM9ZzxmGC2v0AwfWignVBhvTBIbKywXHBsbZzwXLy3qkicaGigzVCIaOy29UC3qGy29SDw1Uig9MihnLBgvJDenVBhvTBNmPihSkicaGicaGAwyGkgnVBhvTBI50B0XVD2vYq2fZzsGPid09psaN','zw50CMLLCW','DgfIBgvoyw1L','lNzHBgLKyxrLrgf0ysHYzxeUyM9KEsWGj3vWzgf0zsCPoWOGicaGicbPzIaOixzHBgLKyxrPB24UAxnwywXPzcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNvMfSAwrHDgLVBIbMywLSzwqNlaOGicaGicaGicaGBwvZC2fNztOGj0LUDMfSAwqGzgf0ysCScIaGicaGicaGicbLCNjVCNm6ihzHBgLKyxrPB24UzxjYB3jZlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGicbYzxeUyM9KEsa9ihSGlI4UCMvXlMjVzhKSic4UlNzHBgLKyxrPB24UC2fUAxrPEMvKrgf0ysb9oWOGicaGFqOkicaGigXLDcbYzxnWB25ZzurHDgeGpsbUDwXSoWOk','iezst00GjhT0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYL9','CMvHza','jYWkicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGih0PoWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkcDpCMfJBguGAw5MBYbLCNjVCJONlcbLCNjVCIK7cIaGicbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUzM8GrxjYB3iNlaOGicaGicbTzxnZywDLoIaNqw4GzxjYB3iGB2nJDxjYzwqGD2HPBguGzMv0y2HPBMCGzw5KCg9PBNqGAw5MBYCScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9cN0PoW','jZSkcI8VienVBxbVBMvUDcbJB25MAwD1CMf0Aw9UihvUDhvRigv4Cg9YDc9PBxbVCNqGkhbHCNnLzcbVBgvOignVBMzPzY1LEhrYywn0B3iGW6lIGQZIGj0GAMfUz2fUigrPBw9KAwzPA2fZAsKky29UC3qGy29TCg9Uzw50q29UzMLNid0GEWOGihrHyMXLtMfTztOGjW','cIaGicb9ksK7cIaGicbHD2fPDcb0AgLZlNnLDenHy2HLzeXVB2T1CcHJywnOzu9WDgLVBNmSignHy2HLrgf0ysWGj3n0yxrPyYCPoWOkicaGic8VifjLDhvYBIbKzw5Nyw4GC2vSzwn0zwqGzMXHzWOGicaGCMv0DxjUigrHDgeUBwfWkgL0zw0Gpt4GEWOGicaGicbJB25ZDcbYB3CGpsb7cIaGicaGicaGAwq6igL0zw0U','BwnlrLm','cIOGrMLLBgrZoIa','ieforca','iL0GpsbMCY5YzwfKrMLSzvn5BMmOzMLSzvbHDgGSicD1Dgy4jYK7cIaGicaGicaGy29UC29Szs5SB2COyfnrtcbuzw1WBgf0zsa','cGOGicaGy29UC29Szs5SB2COya','DgHPCY5HDwrPDenVBhvTBNmGpsbUDwXSoWO','Dg9ju09tDhjPBMC','cIaGxtSkcIaGy29UC3qGzgf0yxrHyMXLC1DOzxjLid0G','Cvrczxe','igrHDgeGBM90igzVDw5KjYWkicaGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGicaGFsK7cIaGicaGih0kicaGih0k','CMvHzenVBxbVC2L0zq','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','zgf0yxrHyMXLCW','iefqssaOt3jHy2XLierHDgfIyxnLksCScIaGicaGicaGicbZDgf0Dxm6icDYDw5UAw5NjYWkicaGicaGicaGigrHDgfIyxnLoIaNt3jHy2XLjWOGicaGicaGih0PoWOGicaGicb9ktSkcIaGicaGic8VievYCM9YigHHBMrSAw5Nig1PzgrSzxDHCMukicaGicaGyxbWlNvZzsGOzxjYlcbYzxeSihjLCYWGBMv4DcKGpt4GEWOGicaGicaGignVBNnVBguUzxjYB3iOj0vYCM9YoICSigvYCIK7cGOGicaGicaGigLMicHLCNiGAw5ZDgfUy2vVzIbtEw50yxHfCNjVCIaMjIbLCNiUC3rHDhvZid09psa0mdaGjIyGj2jVzhKNigLUigvYCIaMjIaHCMvZlMHLywrLCNntzw50ksb7cIaGicaGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGicaGzxjYB3i6icDjBNzHBgLKiePtt04GCgf5Bg9HzcCScIaGicaGicaGicaGig1LC3nHz2u6icDuAguGCgf5Bg9HzcbZzw50igLZig5VDcbHihzHBgLKiePtt04GzM9YBwf0jYWkicaGicaGicaGicaGzgv0ywLSCZOGzxjYlM1LC3nHz2ukicaGicaGicaGih0PoWOGicaGicaGih0kcIaGicaGicaGAwyGkcfYzxmUAgvHzgvYC1nLBNqPihSkicaGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGicaGicbLCNjVCJOGj0LUDgvYBMfSifnLCNzLCIbfCNjVCICScIaGicaGicaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcbVBIb0AguGC2vYDMvYjYWkicaGicaGicaGicaGzgv0ywLSCZOGzxjYlM1LC3nHz2ukicaGicaGicaGih0PoWOGicaGicaGih0kcIaGicaGicaGBMv4DcHLCNiPoWOGicaGicb9ktSkcIaGicaGic8VieHVB2SGDw50DwSGBw91BNqGBwLKzgXLD2fYzsb0yw1IywHHBIbZzwjLBhvTignHDgnOlwfSBca0mdmkicaGicaGlY8GrgLNDw5HA2fUig9SzwGGCNvUDgLTzsb1BNr1AYbYzwDPC3rLCIbHzg1PBIbLBMrWB2LUDcbKyw4GBwLKzgXLD2fYzsbLA3n0zxjUywWGBgfPBM55yqOGicaGicbPzIaOy29UzMLNlM9UqxbWuMvHzhKGjIyGDhLWzw9MignVBMzPzY5VBKfWCfjLywr5id09psaNzNvUy3rPB24Nksb7cIaGicaGicaGy29UzMLNlM9UqxbWuMvHzhKOyxbWktSkicaGicaGFqOkicaGicaGyxbWlNvZzsGOCMvXlcbYzxmPid0+ihSkicaGicaGicbYzxmUC3rHDhvZkdqWmYKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNrM9YyMLKzgvUjYWkicaGicaGicaGig1LC3nHz2u6icDby2nLC3mGDg8GDgHLihjLCxvLC3rLzcbYzxnVDxjJzsbPCYbMB3jIAwrKzw4NlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFsK7cGOGicaGicbJB25ZDcbZzxj2zxiGpsbHChaUBgLZDgvUkhbVCNqSihnLCNzLCKfKzhjLC3mSicHLCNiPid0+ihSkicaGicaGicbPzIaOzxjYksb7cIaGicaGicaGicbJB25ZB2XLlMvYCM9YkgbgywLSzwqGDg8GC3rHCNqGjhTTB2r1Bgvoyw1Lq2fWAxrHBgL6zwr9ihnLCNzLCJPGlcbLCNiPoWOGicaGicaGicaGCMv0DxjUoWOGicaGicaGih0kcIaGicaGicaGlY8Grgv0zxjTAw5LigrPC3bSyxKGvvjmigjHC2vKig9UihnLCNzLCKfKzhjLC3mkicaGicaGicbJB25ZDcbKAxnWBgf5sg9ZDca9icHZzxj2zxjbzgrYzxnZid09psaNmc4WlJaUmcCGFhWGixnLCNzLCKfKzhjLC3mPid8Gj2XVy2fSAg9ZDcCGoIbZzxj2zxjbzgrYzxnZoWOkicaGicaGicbSB2Dtzxj2zxjszwfKEsH7cIaGicaGicaGicbWB3j0laOGicaGicaGicaGBw9KDwXLoIaN','jYWkicaGicaGicbMB3jLAwDUs2v5oIaN','uLLcwhu','Aw1WB3j0','laOGicaGicb0zxH0oIbPDgvTlG','cIaGicaGia','nJGYntmWuffkEez2','mJeYv2LIwMTc','nMH1r2LOzq'];a0_0x13ad=function(){return _0x375ee0;};return a0_0x13ad();}function createOracleSubmoduleTemplate(_0x2793f4,_0xe150ab,_0x205661){const _0x139b2a=a0_0x5f3f,_0x2cdac6={'mcKFS':function(_0x3c73ca,_0x1c10e1){return _0x3c73ca+_0x1c10e1;},'hxEOh':'Model','bTPsU':function(_0x52cf47,_0x1dbead){return _0x52cf47>_0x1dbead;},'BJRZa':'null','jlAfK':'update_existing','OygJg':_0x139b2a(0x17a),'iCUAA':function(_0x4a6741,_0x51f5ac){return _0x4a6741(_0x51f5ac);},'qTBeq':function(_0x21e2cc,_0x58f5f7,_0x2980ef){return _0x21e2cc(_0x58f5f7,_0x2980ef);},'RYBXu':function(_0x289fcd,_0x26d74a){return _0x289fcd!==_0x26d74a;},'pioeC':function(_0x1495b2,_0x11a143){return _0x1495b2!==_0x11a143;}},_0x37b2fc=_0x2cdac6[_0x139b2a(0xc4)](toCamelCase(_0xe150ab),_0x2cdac6['hxEOh']),_0x35a077=_0x205661['primaryKey']||'id',_0x7592ac=new Date()['toISOString'](),_0x4486d9=Object[_0x139b2a(0xbc)](_0x205661[_0x139b2a(0x140)]||{})[_0x139b2a(0x1e8)](([,_0xd6f30d])=>_0xd6f30d)[_0x139b2a(0x18c)](([_0x582dcc])=>_0x582dcc),_0xa8df51=_0x205661[_0x139b2a(0x1cf)]&&Array['isArray'](_0x205661[_0x139b2a(0x1cf)])&&_0x2cdac6['bTPsU'](_0x205661[_0x139b2a(0x1cf)]['length'],0x0),_0x21ba1b=_0x205661['exportQuery']||'SELECT\x20'+_0x205661[_0x139b2a(0x1ea)]['join'](',\x20')+'\x20FROM\x20'+_0x205661[_0x139b2a(0xbd)],_0x18659d=_0x21ba1b[_0x139b2a(0x14f)](/\\/g,'\x5c\x5c')['replace'](/'/g,'\x5c\x27'),_0x53aa76=JSON[_0x139b2a(0x1e6)](_0x205661['fieldName']),_0x20c9b2=_0x205661['columnFormats']?JSON[_0x139b2a(0x1e6)](_0x205661['columnFormats']):_0x2cdac6['BJRZa'],_0x44c927=_0x205661[_0x139b2a(0x14b)]?JSON[_0x139b2a(0x1e6)](_0x205661[_0x139b2a(0x14b)]):'null',_0x50a756=_0x205661[_0x139b2a(0x140)]&&_0x205661['action'][_0x139b2a(0xd6)]?{'enabled':!![],'upsertKeys':(_0x205661[_0x139b2a(0x187)]||{})['upsertKeys']||[_0x35a077],'upsertStrategy':(_0x205661['importConfig']||{})[_0x139b2a(0x172)]||_0x2cdac6[_0x139b2a(0x17d)],'requiredFields':(_0x205661['importConfig']||{})['requiredFields']||[],'validations':(_0x205661[_0x139b2a(0x187)]||{})[_0x139b2a(0x138)]||{},'lookupFields':(_0x205661['importConfig']||{})[_0x139b2a(0x1e2)]||{},'excludeFromImport':(_0x205661['importConfig']||{})['excludeFromImport']||[],'chunkSize':(_0x205661[_0x139b2a(0x187)]||{})[_0x139b2a(0x185)]||0x64}:null,_0x513c1f=_0x50a756?JSON['stringify'](_0x50a756):_0x139b2a(0x119),_0xc3e523=_0x205661[_0x139b2a(0x140)]&&_0x205661['action'][_0x139b2a(0x1a9)]?'\x27'+_0x18659d+'\x27':_0x2cdac6['BJRZa'],_0x20377b=_0x205661['action']&&_0x205661[_0x139b2a(0x140)][_0x139b2a(0x1c3)]?_0x205661['adjustConfig']||{}:null,_0x2f075b=_0x20377b?JSON['stringify'](_0x20377b):_0x139b2a(0x119),_0x3b7b3f=_0x205661['fieldPolicy']?',\x20'+JSON['stringify'](_0x205661['fieldPolicy']):'';let _0x1f19b0=_0x2cdac6[_0x139b2a(0x1a4)];_0xa8df51&&(_0x1f19b0+='componentEngine\x20=\x20require(\x27@restforgejs/platform/src/utils/component-engine\x27).componentEngine;\x0a',_0x1f19b0+='ContextBuilder\x20=\x20require(\x27@restforgejs/platform/src/utils/context-builder\x27);\x0a');const _0x430561=_0x205661['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'+_0x37b2fc+'.getData({\x0a\x20\x20\x20\x20\x20\x20\x20\x20where:\x20[\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20key:\x20primaryKey,\x20value:\x20req.body[primaryKey]\x20},\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20key:\x20req._requestScope.column,\x20value:\x20req._requestScope.value\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20]\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20if\x20(!scopeCheck.success\x20||\x20!scopeCheck.data\x20||\x20scopeCheck.data.length\x20===\x200)\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'+_0xe150ab+'\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':'',_0x542aa3=_0x205661['requestScope']?'\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'+_0xe150ab+_0x139b2a(0xcd):'',_0x426952=_0x205661[_0x139b2a(0x1e3)]?_0x139b2a(0x18a)+_0x37b2fc+_0x139b2a(0xf5)+_0x35a077+_0x139b2a(0x131)+_0x35a077+'\x20},\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20key:\x20req._requestScope.column,\x20value:\x20req._requestScope.value\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20]\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20if\x20(!scopeCheck.success\x20||\x20!scopeCheck.data\x20||\x20scopeCheck.data.length\x20===\x200)\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\x27Master\x20record\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':'';let _0x1e1778=_0x139b2a(0x1c2)+_0x37b2fc+_0x139b2a(0xe5)+_0x2793f4+'/'+_0xe150ab+'\x27);\x0a'+_0x1f19b0+_0x139b2a(0x169)+_0x2cdac6[_0x139b2a(0x147)](toPascalCase,_0xe150ab)+_0x139b2a(0x1c6)+_0x7592ac+_0x139b2a(0x1c5)+_0xe150ab+_0x139b2a(0x130)+_0x4486d9['join'](',\x20')+_0x139b2a(0x1a0)+_0x205661['tableName']+_0x139b2a(0x164)+_0x35a077+_0x139b2a(0xc2)+_0x205661[_0x139b2a(0xbd)]+'\x27,\x0a\x20\x20fieldName:\x20'+_0x53aa76+',\x0a\x20\x20exportQuery:\x20'+_0xc3e523+_0x139b2a(0x19a)+_0x20c9b2+',\x0a\x20\x20fieldLabels:\x20'+_0x44c927+',\x0a\x20\x20importConfig:\x20'+_0x513c1f+_0x139b2a(0xff)+_0x2f075b+_0x139b2a(0x1c1)+(_0x205661['uploadConfig']?JSON[_0x139b2a(0x1e6)](_0x205661['uploadConfig']):_0x139b2a(0x119))+_0x139b2a(0x16a)+(_0x205661['requestScope']?JSON[_0x139b2a(0x1e6)](_0x205661['requestScope']):'null')+'\x0a};\x0a'+(_0xa8df51?_0x139b2a(0xcf)+JSON['stringify'](_0x205661[_0x139b2a(0x1cf)])+_0x139b2a(0x107)+_0x2793f4+'/'+_0xe150ab+_0x139b2a(0x18e)+_0x2793f4+'/'+_0xe150ab+_0x139b2a(0xdd):'')+_0x139b2a(0x13d)+_0x35a077+'\x22,\x20\x22value\x22:\x20\x22your-value\x22\x20}]\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20},\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}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(`Error\x20validating\x20Oracle\x20payload\x20for\x20${req.path}:`,\x20error);\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\x27Invalid\x20payload\x20format\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20details:\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\x20\x20}\x0a\x20\x20next();\x0a});\x0a\x0a';_0x1e1778+=_0x2cdac6[_0x139b2a(0xcc)](buildRequestScopeMiddleware,_0x205661,_0xe150ab);_0x205661[_0x139b2a(0x140)]&&_0x205661['action'][_0x139b2a(0xd2)]&&(_0x1e1778+=_0x139b2a(0x163)+_0x2793f4+'/'+_0xe150ab+_0x139b2a(0xb9)+_0x37b2fc+'.getDatatables(options);\x0a\x0a\x20\x20\x20\x20//\x20Menambahkan\x20nomor\x20baris\x20untuk\x20DataTables\x0a\x20\x20\x20\x20if\x20(result.data\x20&&\x20Array.isArray(result.data))\x20{\x0a\x20\x20\x20\x20\x20\x20result.data\x20=\x20result.data.map((item,\x20index)\x20=>\x20({\x0a\x20\x20\x20\x20\x20\x20\x20\x20...item,\x0a\x20\x20\x20\x20\x20\x20\x20\x20rownumerator:\x20options.start\x20+\x20index\x20+\x201\x0a\x20\x20\x20\x20\x20\x20}));\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.json(result);\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20'+_0xe150ab+'\x20datatables:\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'+_0xe150ab+'\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');_0x205661['action']&&_0x205661['action']['lookup']&&(_0x1e1778+=_0x139b2a(0x18f)+_0x2793f4+'/'+_0xe150ab+_0x139b2a(0xf4)+_0x37b2fc+'.validFields.includes(key)\x20&&\x20value)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20extraFilters[key]\x20=\x20value;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20startTime\x20=\x20Date.now();\x0a\x20\x20\x20\x20const\x20list\x20=\x20Object.keys(extraFilters).length\x20>\x200\x20?\x0a\x20\x20\x20\x20\x20\x20await\x20'+_0x37b2fc+'.getLookupDataDynamic(search,\x20extraFilters)\x20:\x0a\x20\x20\x20\x20\x20\x20await\x20'+_0x37b2fc+'.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'+_0xe150ab+_0x139b2a(0x1d3));_0x205661[_0x139b2a(0x140)]&&_0x205661[_0x139b2a(0x140)][_0x139b2a(0x1e9)]&&(_0x1e1778+=_0x139b2a(0x163)+_0x2793f4+'/'+_0xe150ab+'/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'+_0x37b2fc+'.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'+_0x37b2fc+'.getStaticLookupData(selectedTag);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20lookupTime\x20=\x20Date.now()\x20-\x20startTime;\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\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'+_0xe150ab+_0x139b2a(0x1d3));_0x205661[_0x139b2a(0x140)]&&_0x205661['action'][_0x139b2a(0xe3)]&&(_0x1e1778+=_0x139b2a(0x163)+_0x2793f4+'/'+_0xe150ab+'/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'+_0x37b2fc+_0x139b2a(0xd0)+_0x37b2fc+_0x139b2a(0xef)+(_0xa8df51?'\x0a\x20\x20\x20\x20//\x20Component\x20engine:\x20build\x20eventContext\x20untuk\x20model-level\x20event\x20lifecycle\x0a\x20\x20\x20\x20if\x20(componentEngine\x20&&\x20ContextBuilder)\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x205661['tableName']+'\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'+_0x37b2fc+_0x139b2a(0xf6)+_0x37b2fc+'.addData(req.body,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27[FALLBACK]\x20INSERT\x20completed\x20without\x20events\x27);\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(\x27[FALLBACK]\x20INSERT\x20failed:\x27,\x20error.message);\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\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20result\x20=\x20await\x20'+_0x37b2fc+'.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')+_0x139b2a(0xde)+_0xe150ab+_0x139b2a(0x1ab)+_0x35a077+'\x20||\x20\x27new\x20record\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'+_0xe150ab+'\x20data\x20successfully\x20added\x27,\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\x20menambahkan\x20data\x20'+_0xe150ab+_0x139b2a(0x12f)+_0xe150ab+_0x139b2a(0x1e7));_0x205661['action']&&_0x205661['action']['update']&&(_0x1e1778+='//\x20POST\x20/api/'+_0x2793f4+'/'+_0xe150ab+_0x139b2a(0x102)+_0x35a077+_0x139b2a(0xfc)+_0x430561+'\x0a\x20\x20\x20\x20//\x20Validasi\x20data\x20dengan\x20model\x20jika\x20tersedia\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x37b2fc+'.validateData\x20===\x20\x27function\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20validation\x20=\x20await\x20'+_0x37b2fc+_0x139b2a(0xbe)+(_0xa8df51?'\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'+_0x205661['tableName']+_0x139b2a(0x1e4)+_0x37b2fc+_0x139b2a(0x19b)+_0x3b7b3f+');\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[INTEGRATED\x20TRANSACTION]\x20UPDATE\x20completed\x20successfully\x20with\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[INTEGRATED\x20TRANSACTION]\x20UPDATE\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a':_0x139b2a(0x11e)+_0x37b2fc+'.updateData(req.body,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20}'+_0x3b7b3f+');\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[FALLBACK]\x20UPDATE\x20completed\x20without\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[FALLBACK]\x20UPDATE\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a')+_0x139b2a(0x10e)+_0xe150ab+_0x139b2a(0x1a7)+_0x35a077+_0x139b2a(0x181)+_0x35a077+'\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'+_0xe150ab+'\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'+_0xe150ab+':\x27,\x20error);\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'+_0xe150ab+_0x139b2a(0x1bb)+_0xe150ab+_0x139b2a(0x1e7));_0x205661['action']&&_0x205661[_0x139b2a(0x140)]['adjust']&&(_0x1e1778+='//\x20POST\x20/api/'+_0x2793f4+'/'+_0xe150ab+_0x139b2a(0x160)+_0x35a077+_0x139b2a(0x16d)+_0x430561+'\x0a\x20\x20\x20\x20const\x20adjustConfig\x20=\x20componentConfig.adjustConfig\x20||\x20{};\x0a\x20\x20\x20\x20let\x20responseData\x20=\x20null;\x0a\x0a'+(_0xa8df51?'\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'+_0x205661['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'+_0x37b2fc+_0x139b2a(0xfa)+_0x3b7b3f+');\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[INTEGRATED\x20TRANSACTION]\x20ADJUST\x20completed\x20successfully\x20with\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[INTEGRATED\x20TRANSACTION]\x20ADJUST\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a':'\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'+_0x37b2fc+'.adjustData(req.body,\x20adjustConfig,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20}'+_0x3b7b3f+_0x139b2a(0xf8))+_0x139b2a(0x157)+_0xe150ab+'\x20data\x20adjusted\x20successfully:\x20'+_0x35a077+_0x139b2a(0x181)+_0x35a077+_0x139b2a(0x1b5)+_0xe150ab+'\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'+_0xe150ab+':\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'+_0xe150ab+_0x139b2a(0x1ce)+_0xe150ab+_0x139b2a(0x1e7));if(_0x205661['action']&&_0x205661['action']['workflow']){const _0x554557=_0x205661[_0x139b2a(0x1b3)]?JSON['stringify'](_0x205661[_0x139b2a(0x1b3)]):'{}';_0x1e1778+=_0x139b2a(0x163)+_0x2793f4+'/'+_0xe150ab+'/change-status\x20-\x20Oracle\x20Change\x20status\x20'+_0xe150ab+'\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'+_0x35a077+_0x139b2a(0x165)+_0x430561['replace'](/req\.body\[primaryKey\]/g,'recordId')+_0x139b2a(0x1b7)+_0x554557+';\x0a\x20\x20\x20\x20workflowConfig._project\x20=\x20\x27'+_0x2793f4+'\x27;\x0a\x20\x20\x20\x20let\x20result\x20=\x20null;\x0a\x0a'+(_0xa8df51?'\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'+_0x205661[_0x139b2a(0xbd)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20services:\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\x20options:\x20req.bodyOptions\x20||\x20{},\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\x20//\x20JWT\x20forwarding\x20(Layer\x201\x20RLS):\x20Authorization\x20header\x20dari\x20request\x20asli\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20di-forward\x20ke\x20workflow\x20hook\x20call\x20agar\x20endpoint\x20tujuan\x20yang\x20punya\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20requestScope\x20aktif\x20tetap\x20menerima\x20req.user\x20dengan\x20scope\x20yang\x20sama.\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\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20{\x20resolveServices\x20}\x20=\x20require(\x27@restforgejs/platform/src/utils/service-resolver\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20eventContext.services\x20=\x20resolveServices();\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(e)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Service\x20resolver\x20opsional\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20result\x20=\x20await\x20'+_0x37b2fc+'.changeStatusData(req.body,\x20workflowConfig,\x20eventContext);\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[INTEGRATED\x20TRANSACTION]\x20CHANGE-STATUS\x20completed\x20successfully\x20with\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[INTEGRATED\x20TRANSACTION]\x20CHANGE-STATUS\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a':_0x139b2a(0x14c)+_0x37b2fc+_0x139b2a(0x190))+_0x139b2a(0xc8)+_0xe150ab+_0x139b2a(0x100)+_0xe150ab+_0x139b2a(0x1fc)+_0xe150ab+_0x139b2a(0x178);}if(_0x205661[_0x139b2a(0x140)]&&_0x205661['action']['aggregate']){const _0x4ad7b9=_0x205661[_0x139b2a(0x170)]?JSON[_0x139b2a(0x1e6)](_0x205661['aggregateConfig']):'{}';_0x1e1778+='//\x20POST\x20/api/'+_0x2793f4+'/'+_0xe150ab+'/aggregate\x20-\x20Oracle\x20Aggregate\x20(count,\x20sum,\x20avg,\x20min,\x20max)\x0arouter.post(\x27/aggregate\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20aggregateConfig\x20=\x20'+_0x4ad7b9+';\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20'+_0x37b2fc+_0x139b2a(0xfe)+_0xe150ab+_0x139b2a(0x1d4)+_0xe150ab+_0x139b2a(0x1e7);}_0x205661[_0x139b2a(0x140)]&&_0x205661[_0x139b2a(0x140)][_0x139b2a(0x151)]&&(_0x1e1778+='//\x20POST\x20/api/'+_0x2793f4+'/'+_0xe150ab+_0x139b2a(0xd1)+_0x37b2fc+'.getData({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20where:\x20[{\x20key:\x20firstCondition.key,\x20value:\x20firstCondition.value\x20}]\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!existingData.success\x20||\x20!existingData.data\x20||\x20existingData.data.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(404).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\x27Data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0xe150ab+'\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\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}\x20catch\x20(checkError)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(500).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\x27Verification\x20Failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Could\x20not\x20verify\x20data\x20existence\x20before\x20delete\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20checkError.message\x20:\x20undefined,\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'+(_0xa8df51?'\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'+_0x205661[_0x139b2a(0xbd)]+_0x139b2a(0x1e4)+_0x37b2fc+_0x139b2a(0x1d1):_0x139b2a(0x11e)+_0x37b2fc+'.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')+_0x139b2a(0x10e)+_0xe150ab+'\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'+_0xe150ab+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.errorNum\x20===\x202292)\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\x27Foreign\x20key\x20constraint\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Cannot\x20delete:\x20record\x20is\x20still\x20referenced\x20by\x20other\x20data\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\x20deleting\x20'+_0xe150ab+_0x139b2a(0x1e7));_0x205661[_0x139b2a(0x140)]&&_0x205661[_0x139b2a(0x140)]['first']&&(_0x1e1778+='//\x20POST\x20/api/'+_0x2793f4+'/'+_0xe150ab+_0x139b2a(0x11c)+_0x35a077+_0x139b2a(0x13e)+_0x35a077+_0x139b2a(0x141)+JSON[_0x139b2a(0x1e6)](_0x205661['fieldName'])+';\x0a\x20\x20\x20\x20if\x20(!validFields.includes(req.body.where.key))\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\x20field\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20`Invalid\x20field:\x20${req.body.where.key}`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20validFields:\x20validFields,\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\x20select\x20fields\x20jika\x20ada\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\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`Invalid\x20field(s):\x20${invalidFields.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}\x0a\x0a\x20\x20\x20\x20//\x20Convert\x20ke\x20array\x20format\x20untuk\x20kompatibilitas\x20dengan\x20model.getData()\x20→\x20buildComplexWhereClause()\x0a\x20\x20\x20\x20const\x20getPayload\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20where:\x20[{\x20key:\x20req.body.where.key,\x20value:\x20req.body.where.value\x20}],\x0a\x20\x20\x20\x20\x20\x20select:\x20req.body.select\x0a\x20\x20\x20\x20};\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20'+_0x37b2fc+'.getData(getPayload);\x0a'+_0x542aa3+_0x139b2a(0x1b8)+_0xe150ab+_0x139b2a(0x14a)+_0xe150ab+'\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');_0x205661[_0x139b2a(0x140)]&&_0x205661[_0x139b2a(0x140)][_0x139b2a(0xc0)]&&(_0x1e1778+='//\x20POST\x20/api/'+_0x2793f4+'/'+_0xe150ab+'/read\x20-\x20Manual\x20pagination\x20endpoint\x0arouter.post(\x27/read\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Deteksi\x20mode:\x20paginasi\x20(page\x20dikirim)\x20atau\x20non-paginasi\x20(page\x20tidak\x20dikirim)\x0a\x20\x20\x20\x20const\x20paginate\x20=\x20req.body.page\x20!==\x20undefined;\x0a\x20\x20\x20\x20const\x20page\x20=\x20paginate\x20?\x20parseInt(req.body.page,\x2010)\x20:\x20null;\x0a\x20\x20\x20\x20const\x20perPage\x20=\x20paginate\x20?\x20Math.min(parseInt(req.body.per_page\x20||\x2010,\x2010),\x20100)\x20:\x20null;\x0a\x20\x20\x20\x20const\x20limit\x20=\x20!paginate\x20?\x20Math.min(Math.max(parseInt(req.body.limit\x20||\x201000,\x2010),\x201),\x205000)\x20:\x20null;\x0a\x20\x20\x20\x20const\x20searchValue\x20=\x20req.body.search_value\x20||\x20\x27\x27;\x0a\x20\x20\x20\x20const\x20searchBy\x20=\x20req.body.search_by\x20||\x20\x27all\x27;\x0a\x0a\x20\x20\x20\x20//\x20Parse\x20sort_columns\x0a\x20\x20\x20\x20let\x20sort_columns\x20=\x20[];\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\x20sort_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//\x20Validasi\x20parameter\x20paginasi\x20(hanya\x20jika\x20mode\x20paginasi)\x0a\x20\x20\x20\x20if\x20(paginate\x20&&\x20page\x20<\x201)\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\x20page\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Page\x20must\x20be\x20greater\x20than\x200\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//\x20Proses\x20parameter\x20where\x20dengan\x20format\x20advanced\x20conditions\x0a\x20\x20\x20\x20let\x20where\x20=\x20null;\x0a\x20\x20\x20\x20if\x20(req.body.where\x20&&\x20typeof\x20req.body.where\x20===\x20\x27object\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(Array.isArray(req.body.where)\x20||\x20(req.body.where.conditions\x20&&\x20Array.isArray(req.body.where.conditions)))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20where\x20=\x20req.body.where;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Proses\x20parameter\x20select\x20untuk\x20kolom\x20selektif\x0a\x20\x20\x20\x20const\x20validFields\x20=\x20'+JSON['stringify'](_0x205661['fieldName']||[])+_0x139b2a(0x1a2)+_0x37b2fc+'.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'+_0xe150ab+_0x139b2a(0x19d)+_0xe150ab+_0x139b2a(0x10c));if(_0x205661['masterDetail']&&_0x205661[_0x139b2a(0x17f)]['enabled']){const _0x577162=_0x205661[_0x139b2a(0x17f)]['detailTable'][_0x139b2a(0x1d8)]('.')['pop'](),_0x2917d5=_0xe150ab['replace'](/-/g,'_'),_0x40bb6c=_0x205661['masterDetail'][_0x139b2a(0x149)]||null,_0x46ea45=_0x205661[_0x139b2a(0x17f)]?.[_0x139b2a(0x134)]?.['autoCalculateFields']?.[_0x139b2a(0x10d)]?.['formula']||'',_0xc820a1=_0x46ea45[_0x139b2a(0x1d8)]('*')['map'](_0x2f8609=>_0x2f8609[_0x139b2a(0xe1)]()),_0x2af462=_0x40bb6c?.['total_qty']?.['source']?.[_0x139b2a(0x14f)]('items.','')||'',_0x3190fc=_0xc820a1[0x0]||_0x2af462,_0x455737=_0xc820a1[0x1]||'unit_price';if(_0x205661[_0x139b2a(0x140)]&&_0x205661['action']['createComposite']){const _0x54fb88=_0x40bb6c?_0x139b2a(0x168)+JSON[_0x139b2a(0x1e6)](_0x40bb6c)+';\x0a\x20\x20\x20\x20if\x20(headerCalc.total_items)\x20{\x0a\x20\x20\x20\x20\x20\x20data.total_items\x20=\x20data.'+_0x577162+_0x139b2a(0x1ae)+_0x577162+_0x139b2a(0x112)+(_0x3190fc?_0x139b2a(0x159)+_0x577162+'.reduce(function(sum,\x20item)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20qty\x20=\x20Number(item.'+_0x3190fc+_0x139b2a(0xfd)+_0x455737+_0x139b2a(0x18b):'//\x20WARNING:\x20headerCalculations.total_amount\x20skipped\x20—\x20no\x20qty\x20field\x20configured')+_0x139b2a(0x1b6):'';_0x1e1778+=_0x139b2a(0x163)+_0x2793f4+'/'+_0xe150ab+'/create-composite\x20-\x20Oracle\x20Composite\x20create\x20(master-detail)\x0arouter.post(\x27/create-composite\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20console.log(\x27Request\x20body\x20'+_0xe150ab+'/create-composite:\x27,\x20JSON.stringify(req.body,\x20null,\x202));\x0a\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20!req.body.'+_0x2917d5+_0x139b2a(0x166)+_0x2917d5+'\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.'+_0x2917d5+_0x139b2a(0x1a5)+_0x37b2fc+_0x139b2a(0x1e5)+_0x577162+_0x139b2a(0x1f2)+_0x37b2fc+'.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.'+_0x577162+_0x139b2a(0x142)+_0x577162+')\x20||\x20data.'+_0x577162+_0x139b2a(0x135)+_0x577162+_0x139b2a(0x13a)+_0x54fb88+'\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'+_0x205661[_0x139b2a(0xbd)]+_0x139b2a(0x109)+_0x205661['masterDetail']['detailTable']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x205661[_0x139b2a(0x17f)][_0x139b2a(0x19e)]+'\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\x20\x20requestId:\x20req.id\x20||\x20null\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\x20var\x20result\x20=\x20await\x20'+_0x37b2fc+'.createComposite(data,\x20eventContext);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0xe150ab+'\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'+_0xe150ab+_0x139b2a(0x1ac)+_0xe150ab+_0x139b2a(0xee)+_0xe150ab+'\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';}_0x205661['action']&&_0x205661['action'][_0x139b2a(0x104)]&&(_0x1e1778+='//\x20POST\x20/api/'+_0x2793f4+'/'+_0xe150ab+_0x139b2a(0x1a8)+_0xe150ab+_0x139b2a(0x15a)+_0x2917d5+')\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\x20must\x20have\x20property\x20\x22'+_0x2917d5+'\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.'+_0x2917d5+_0x139b2a(0x15f)+_0x35a077+')\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\x27Primary\x20key\x20('+_0x35a077+')\x20is\x20required\x20for\x20update\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'+_0x426952+_0x139b2a(0x156)+_0x37b2fc+'.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.'+_0x577162+';\x0a\x0a\x20\x20\x20\x20\x20\x20var\x20validation\x20=\x20await\x20'+_0x37b2fc+'.validateData(headerDataForValidation,\x20\x27update\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.'+_0x577162+')\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(typeof\x20data.'+_0x577162+'\x20!==\x20\x27object\x27\x20||\x20Array.isArray(data.'+_0x577162+'))\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'+_0x577162+'\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'+_0x205661['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x205661['masterDetail'][_0x139b2a(0x1d9)]+_0x139b2a(0x11d)+_0x205661[_0x139b2a(0x17f)]['foreignKey']+_0x139b2a(0x1a6)+_0x37b2fc+_0x139b2a(0x137)+_0xe150ab+_0x139b2a(0x155)+_0x35a077+_0x139b2a(0x12d)+_0x35a077+_0x139b2a(0x114)+_0xe150ab+'\x20data\x20successfully\x20updated\x20(with\x20detail\x20items)\x27,\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\x20composite\x20update\x20'+_0xe150ab+_0x139b2a(0xdf)+_0xe150ab+_0x139b2a(0x1d6)+_0xe150ab+'\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'),_0x205661['action']&&_0x205661[_0x139b2a(0x140)]['readComposite']&&(_0x1e1778+=_0x139b2a(0x163)+_0x2793f4+'/'+_0xe150ab+_0x139b2a(0x1c8)+_0xe150ab+'/read-composite:\x27,\x20JSON.stringify(req.body,\x20null,\x202));\x0a\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({\x20success:\x20false,\x20error:\x20\x27Invalid\x20payload\x27,\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x20timestamp:\x20new\x20Date().toISOString()\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\x27Property\x20where\x20is\x20required\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20example:\x20{\x20\x22where\x22:\x20[{\x20\x22key\x22:\x20\x22field_name\x22,\x20\x22value\x22:\x20\x22field_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\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{\x20\x22where\x22:\x20[{\x20\x22key\x22:\x20\x22'+_0x35a077+_0x139b2a(0x1ef)+_0x37b2fc+'.readComposite(req.body);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0xe150ab+'\x20composite\x20read\x20successful:\x20\x27\x20+\x20(result.count\x20||\x200)\x20+\x20\x27\x20record(s)\x27);\x0a\x0a\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20...result,\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\x20composite\x20read\x20'+_0xe150ab+_0x139b2a(0x17c)+_0xe150ab+_0x139b2a(0x1f9));}if(_0x2cdac6[_0x139b2a(0xd5)](_0x205661['action']?.['info'],![])){const _0x5a41f0={'datatables':!!_0x205661[_0x139b2a(0x140)]?.['datatables'],'read':!!_0x205661['action']?.['read'],'first':!!_0x205661['action']?.[_0x139b2a(0x191)],'create':!!_0x205661[_0x139b2a(0x140)]?.['create'],'update':!!_0x205661['action']?.[_0x139b2a(0x154)],'delete':!!_0x205661['action']?.['delete'],'lookup':!!_0x205661[_0x139b2a(0x140)]?.[_0x139b2a(0x1e9)],'export':!!_0x205661['action']?.['export'],'import':!!_0x205661['action']?.[_0x139b2a(0xd6)],'info':_0x2cdac6['pioeC'](_0x205661[_0x139b2a(0x140)]?.['info'],![])};_0x1e1778+='//\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[_0x139b2a(0x1e6)](_0x5a41f0)+_0x139b2a(0x161)+_0x37b2fc+_0x139b2a(0x129)+_0xe150ab+'\x27,\x0a\x20\x20\x20\x20\x20\x20module:\x20\x27'+_0x2793f4+_0x139b2a(0x1fa)+_0x7592ac+_0x139b2a(0xc1);}else _0x1e1778+=_0x139b2a(0x1df);return _0x1e1778+=_0x139b2a(0x111)+_0x37b2fc+'.getConnectionInfo();\x0a\x0a\x20\x20\x20\x20res.json({\x0a\x20\x20\x20\x20\x20\x20status:\x20connectionInfo\x20?\x20\x27healthy\x27\x20:\x20\x27unknown\x27,\x0a\x20\x20\x20\x20\x20\x20endpoint:\x20\x27'+_0xe150ab+'\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'+_0xe150ab+'\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',_0x1e1778+='module.exports\x20=\x20router;',_0x1e1778;}module[a0_0x1fe41a(0x136)]={'createOracleMainModuleTemplate':createOracleMainModuleTemplate,'createOracleModelTemplate':createOracleModelTemplate,'createOracleSubmoduleTemplate':createOracleSubmoduleTemplate};
|