@shohaghinfo/aerojs 0.1.0
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/AEROJS.md +12 -0
- package/AGENTS.md +709 -0
- package/LICENSE +21 -0
- package/README.md +1186 -0
- package/bin/aero.js +24 -0
- package/dist/application.d.ts +251 -0
- package/dist/application.d.ts.map +1 -0
- package/dist/application.js +658 -0
- package/dist/application.js.map +1 -0
- package/dist/audit/audit-trail.d.ts +122 -0
- package/dist/audit/audit-trail.d.ts.map +1 -0
- package/dist/audit/audit-trail.js +154 -0
- package/dist/audit/audit-trail.js.map +1 -0
- package/dist/auth/auth-manager.d.ts +95 -0
- package/dist/auth/auth-manager.d.ts.map +1 -0
- package/dist/auth/auth-manager.js +130 -0
- package/dist/auth/auth-manager.js.map +1 -0
- package/dist/auth/bouncer.d.ts +143 -0
- package/dist/auth/bouncer.d.ts.map +1 -0
- package/dist/auth/bouncer.js +243 -0
- package/dist/auth/bouncer.js.map +1 -0
- package/dist/auth/guards/api-token-guard.d.ts +68 -0
- package/dist/auth/guards/api-token-guard.d.ts.map +1 -0
- package/dist/auth/guards/api-token-guard.js +107 -0
- package/dist/auth/guards/api-token-guard.js.map +1 -0
- package/dist/auth/guards/guard.d.ts +35 -0
- package/dist/auth/guards/guard.d.ts.map +1 -0
- package/dist/auth/guards/guard.js +7 -0
- package/dist/auth/guards/guard.js.map +1 -0
- package/dist/auth/guards/jwt-guard.d.ts +64 -0
- package/dist/auth/guards/jwt-guard.d.ts.map +1 -0
- package/dist/auth/guards/jwt-guard.js +104 -0
- package/dist/auth/guards/jwt-guard.js.map +1 -0
- package/dist/auth/guards/session-guard.d.ts +57 -0
- package/dist/auth/guards/session-guard.d.ts.map +1 -0
- package/dist/auth/guards/session-guard.js +101 -0
- package/dist/auth/guards/session-guard.js.map +1 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/policy.d.ts +20 -0
- package/dist/auth/policy.d.ts.map +1 -0
- package/dist/auth/policy.js +49 -0
- package/dist/auth/policy.js.map +1 -0
- package/dist/body-parser.d.ts +40 -0
- package/dist/body-parser.d.ts.map +1 -0
- package/dist/body-parser.js +99 -0
- package/dist/body-parser.js.map +1 -0
- package/dist/cache/cache-driver.d.ts +43 -0
- package/dist/cache/cache-driver.d.ts.map +1 -0
- package/dist/cache/cache-driver.js +6 -0
- package/dist/cache/cache-driver.js.map +1 -0
- package/dist/cache/cache-manager.d.ts +44 -0
- package/dist/cache/cache-manager.d.ts.map +1 -0
- package/dist/cache/cache-manager.js +160 -0
- package/dist/cache/cache-manager.js.map +1 -0
- package/dist/cache/drivers/database-cache-driver.d.ts +18 -0
- package/dist/cache/drivers/database-cache-driver.d.ts.map +1 -0
- package/dist/cache/drivers/database-cache-driver.js +77 -0
- package/dist/cache/drivers/database-cache-driver.js.map +1 -0
- package/dist/cache/drivers/file-cache-driver.d.ts +21 -0
- package/dist/cache/drivers/file-cache-driver.d.ts.map +1 -0
- package/dist/cache/drivers/file-cache-driver.js +95 -0
- package/dist/cache/drivers/file-cache-driver.js.map +1 -0
- package/dist/cache/drivers/memory-cache-driver.d.ts +21 -0
- package/dist/cache/drivers/memory-cache-driver.d.ts.map +1 -0
- package/dist/cache/drivers/memory-cache-driver.js +82 -0
- package/dist/cache/drivers/memory-cache-driver.js.map +1 -0
- package/dist/cache/drivers/redis-cache-driver.d.ts +18 -0
- package/dist/cache/drivers/redis-cache-driver.d.ts.map +1 -0
- package/dist/cache/drivers/redis-cache-driver.js +95 -0
- package/dist/cache/drivers/redis-cache-driver.js.map +1 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +7 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +10 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +471 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/make.d.ts +16 -0
- package/dist/cli/commands/make.d.ts.map +1 -0
- package/dist/cli/commands/make.js +157 -0
- package/dist/cli/commands/make.js.map +1 -0
- package/dist/cli/commands/new.d.ts +7 -0
- package/dist/cli/commands/new.d.ts.map +1 -0
- package/dist/cli/commands/new.js +175 -0
- package/dist/cli/commands/new.js.map +1 -0
- package/dist/cli/commands/vault.d.ts +7 -0
- package/dist/cli/commands/vault.d.ts.map +1 -0
- package/dist/cli/commands/vault.js +39 -0
- package/dist/cli/commands/vault.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +8 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/runner.d.ts +10 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +207 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/cli/templates/ecommerce-erp.d.ts +6 -0
- package/dist/cli/templates/ecommerce-erp.d.ts.map +1 -0
- package/dist/cli/templates/ecommerce-erp.js +1760 -0
- package/dist/cli/templates/ecommerce-erp.js.map +1 -0
- package/dist/cli/templates/fullstack-templates.d.ts +17 -0
- package/dist/cli/templates/fullstack-templates.d.ts.map +1 -0
- package/dist/cli/templates/fullstack-templates.js +17 -0
- package/dist/cli/templates/fullstack-templates.js.map +1 -0
- package/dist/config/config.d.ts +25 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +53 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/env-schema.d.ts +62 -0
- package/dist/config/env-schema.d.ts.map +1 -0
- package/dist/config/env-schema.js +203 -0
- package/dist/config/env-schema.js.map +1 -0
- package/dist/config/env.d.ts +31 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +153 -0
- package/dist/config/env.js.map +1 -0
- package/dist/context.d.ts +181 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +277 -0
- package/dist/context.js.map +1 -0
- package/dist/core/application-core.d.ts +79 -0
- package/dist/core/application-core.d.ts.map +1 -0
- package/dist/core/application-core.js +560 -0
- package/dist/core/application-core.js.map +1 -0
- package/dist/core/application.d.ts +121 -0
- package/dist/core/application.d.ts.map +1 -0
- package/dist/core/application.js +179 -0
- package/dist/core/application.js.map +1 -0
- package/dist/core/body-parser.d.ts +12 -0
- package/dist/core/body-parser.d.ts.map +1 -0
- package/dist/core/body-parser.js +85 -0
- package/dist/core/body-parser.js.map +1 -0
- package/dist/core/context.d.ts +94 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +278 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/error-dashboard.d.ts +35 -0
- package/dist/core/error-dashboard.d.ts.map +1 -0
- package/dist/core/error-dashboard.js +481 -0
- package/dist/core/error-dashboard.js.map +1 -0
- package/dist/core/errors.d.ts +83 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +117 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/hooks.d.ts +39 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/hooks.js +65 -0
- package/dist/core/hooks.js.map +1 -0
- package/dist/core/middleware.d.ts +15 -0
- package/dist/core/middleware.d.ts.map +1 -0
- package/dist/core/middleware.js +45 -0
- package/dist/core/middleware.js.map +1 -0
- package/dist/core/request.d.ts +50 -0
- package/dist/core/request.d.ts.map +1 -0
- package/dist/core/request.js +94 -0
- package/dist/core/request.js.map +1 -0
- package/dist/core/response.d.ts +49 -0
- package/dist/core/response.d.ts.map +1 -0
- package/dist/core/response.js +302 -0
- package/dist/core/response.js.map +1 -0
- package/dist/core/router.d.ts +58 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/router.js +298 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/types.d.ts +70 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +6 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/utils.d.ts +36 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +108 -0
- package/dist/core/utils.js.map +1 -0
- package/dist/database/connection.d.ts +97 -0
- package/dist/database/connection.d.ts.map +1 -0
- package/dist/database/connection.js +251 -0
- package/dist/database/connection.js.map +1 -0
- package/dist/database/drizzle.d.ts +10 -0
- package/dist/database/drizzle.d.ts.map +1 -0
- package/dist/database/drizzle.js +13 -0
- package/dist/database/drizzle.js.map +1 -0
- package/dist/database/factory.d.ts +39 -0
- package/dist/database/factory.d.ts.map +1 -0
- package/dist/database/factory.js +74 -0
- package/dist/database/factory.js.map +1 -0
- package/dist/database/index.d.ts +16 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +16 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/knex.d.ts +33 -0
- package/dist/database/knex.d.ts.map +1 -0
- package/dist/database/knex.js +123 -0
- package/dist/database/knex.js.map +1 -0
- package/dist/database/migrator.d.ts +29 -0
- package/dist/database/migrator.d.ts.map +1 -0
- package/dist/database/migrator.js +83 -0
- package/dist/database/migrator.js.map +1 -0
- package/dist/database/model-hooks.d.ts +61 -0
- package/dist/database/model-hooks.d.ts.map +1 -0
- package/dist/database/model-hooks.js +126 -0
- package/dist/database/model-hooks.js.map +1 -0
- package/dist/database/model.d.ts +279 -0
- package/dist/database/model.d.ts.map +1 -0
- package/dist/database/model.js +960 -0
- package/dist/database/model.js.map +1 -0
- package/dist/database/prisma.d.ts +13 -0
- package/dist/database/prisma.d.ts.map +1 -0
- package/dist/database/prisma.js +28 -0
- package/dist/database/prisma.js.map +1 -0
- package/dist/database/query-builder.d.ts +178 -0
- package/dist/database/query-builder.d.ts.map +1 -0
- package/dist/database/query-builder.js +607 -0
- package/dist/database/query-builder.js.map +1 -0
- package/dist/database/schema.d.ts +38 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +134 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/database/seeder.d.ts +24 -0
- package/dist/database/seeder.d.ts.map +1 -0
- package/dist/database/seeder.js +27 -0
- package/dist/database/seeder.js.map +1 -0
- package/dist/di/container.d.ts +52 -0
- package/dist/di/container.d.ts.map +1 -0
- package/dist/di/container.js +124 -0
- package/dist/di/container.js.map +1 -0
- package/dist/di/decorators.d.ts +17 -0
- package/dist/di/decorators.d.ts.map +1 -0
- package/dist/di/decorators.js +63 -0
- package/dist/di/decorators.js.map +1 -0
- package/dist/di/inject.d.ts +11 -0
- package/dist/di/inject.d.ts.map +1 -0
- package/dist/di/inject.js +30 -0
- package/dist/di/inject.js.map +1 -0
- package/dist/di/provider.d.ts +32 -0
- package/dist/di/provider.d.ts.map +1 -0
- package/dist/di/provider.js +30 -0
- package/dist/di/provider.js.map +1 -0
- package/dist/diagnostics/diagnostics-plugin.d.ts +9 -0
- package/dist/diagnostics/diagnostics-plugin.d.ts.map +1 -0
- package/dist/diagnostics/diagnostics-plugin.js +49 -0
- package/dist/diagnostics/diagnostics-plugin.js.map +1 -0
- package/dist/diagnostics/health-check.d.ts +51 -0
- package/dist/diagnostics/health-check.d.ts.map +1 -0
- package/dist/diagnostics/health-check.js +122 -0
- package/dist/diagnostics/health-check.js.map +1 -0
- package/dist/diagnostics/index.d.ts +4 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +4 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/diagnostics/prometheus.d.ts +19 -0
- package/dist/diagnostics/prometheus.d.ts.map +1 -0
- package/dist/diagnostics/prometheus.js +99 -0
- package/dist/diagnostics/prometheus.js.map +1 -0
- package/dist/errors.d.ts +139 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +173 -0
- package/dist/errors.js.map +1 -0
- package/dist/events/event-bus.d.ts +178 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +182 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/graphql/adapter.d.ts +48 -0
- package/dist/graphql/adapter.d.ts.map +1 -0
- package/dist/graphql/adapter.js +109 -0
- package/dist/graphql/adapter.js.map +1 -0
- package/dist/graphql/executor.d.ts +24 -0
- package/dist/graphql/executor.d.ts.map +1 -0
- package/dist/graphql/executor.js +124 -0
- package/dist/graphql/executor.js.map +1 -0
- package/dist/graphql/index.d.ts +8 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +8 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/lexer.d.ts +52 -0
- package/dist/graphql/lexer.d.ts.map +1 -0
- package/dist/graphql/lexer.js +188 -0
- package/dist/graphql/lexer.js.map +1 -0
- package/dist/graphql/parser.d.ts +29 -0
- package/dist/graphql/parser.d.ts.map +1 -0
- package/dist/graphql/parser.js +206 -0
- package/dist/graphql/parser.js.map +1 -0
- package/dist/graphql/plugin.d.ts +13 -0
- package/dist/graphql/plugin.d.ts.map +1 -0
- package/dist/graphql/plugin.js +103 -0
- package/dist/graphql/plugin.js.map +1 -0
- package/dist/graphql/schema.d.ts +15 -0
- package/dist/graphql/schema.d.ts.map +1 -0
- package/dist/graphql/schema.js +17 -0
- package/dist/graphql/schema.js.map +1 -0
- package/dist/graphql/types.d.ts +65 -0
- package/dist/graphql/types.d.ts.map +1 -0
- package/dist/graphql/types.js +74 -0
- package/dist/graphql/types.js.map +1 -0
- package/dist/hooks.d.ts +67 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +115 -0
- package/dist/hooks.js.map +1 -0
- package/dist/i18n/i18n.d.ts +42 -0
- package/dist/i18n/i18n.d.ts.map +1 -0
- package/dist/i18n/i18n.js +158 -0
- package/dist/i18n/i18n.js.map +1 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/inertia/inertia.d.ts +62 -0
- package/dist/inertia/inertia.d.ts.map +1 -0
- package/dist/inertia/inertia.js +159 -0
- package/dist/inertia/inertia.js.map +1 -0
- package/dist/logging/index.d.ts +7 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +7 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +62 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +197 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/request-context.d.ts +29 -0
- package/dist/logging/request-context.d.ts.map +1 -0
- package/dist/logging/request-context.js +29 -0
- package/dist/logging/request-context.js.map +1 -0
- package/dist/mail/drivers/log-mail-driver.d.ts +11 -0
- package/dist/mail/drivers/log-mail-driver.d.ts.map +1 -0
- package/dist/mail/drivers/log-mail-driver.js +23 -0
- package/dist/mail/drivers/log-mail-driver.js.map +1 -0
- package/dist/mail/drivers/mail-driver.d.ts +14 -0
- package/dist/mail/drivers/mail-driver.d.ts.map +1 -0
- package/dist/mail/drivers/mail-driver.js +6 -0
- package/dist/mail/drivers/mail-driver.js.map +1 -0
- package/dist/mail/drivers/memory-mail-driver.d.ts +14 -0
- package/dist/mail/drivers/memory-mail-driver.d.ts.map +1 -0
- package/dist/mail/drivers/memory-mail-driver.js +24 -0
- package/dist/mail/drivers/memory-mail-driver.js.map +1 -0
- package/dist/mail/index.d.ts +10 -0
- package/dist/mail/index.d.ts.map +1 -0
- package/dist/mail/index.js +10 -0
- package/dist/mail/index.js.map +1 -0
- package/dist/mail/mailer.d.ts +48 -0
- package/dist/mail/mailer.d.ts.map +1 -0
- package/dist/mail/mailer.js +111 -0
- package/dist/mail/mailer.js.map +1 -0
- package/dist/mail/message.d.ts +30 -0
- package/dist/mail/message.d.ts.map +1 -0
- package/dist/mail/message.js +77 -0
- package/dist/mail/message.js.map +1 -0
- package/dist/middleware/cors.d.ts +16 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +76 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware.d.ts +26 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +45 -0
- package/dist/middleware.js.map +1 -0
- package/dist/pdf/index.d.ts +2 -0
- package/dist/pdf/index.d.ts.map +1 -0
- package/dist/pdf/index.js +2 -0
- package/dist/pdf/index.js.map +1 -0
- package/dist/pdf/pdf.d.ts +86 -0
- package/dist/pdf/pdf.d.ts.map +1 -0
- package/dist/pdf/pdf.js +237 -0
- package/dist/pdf/pdf.js.map +1 -0
- package/dist/plugins/plugin.d.ts +25 -0
- package/dist/plugins/plugin.d.ts.map +1 -0
- package/dist/plugins/plugin.js +17 -0
- package/dist/plugins/plugin.js.map +1 -0
- package/dist/queue/drivers/database-queue-driver.d.ts +21 -0
- package/dist/queue/drivers/database-queue-driver.d.ts.map +1 -0
- package/dist/queue/drivers/database-queue-driver.js +84 -0
- package/dist/queue/drivers/database-queue-driver.js.map +1 -0
- package/dist/queue/drivers/memory-queue-driver.d.ts +17 -0
- package/dist/queue/drivers/memory-queue-driver.d.ts.map +1 -0
- package/dist/queue/drivers/memory-queue-driver.js +71 -0
- package/dist/queue/drivers/memory-queue-driver.js.map +1 -0
- package/dist/queue/drivers/queue-driver.d.ts +44 -0
- package/dist/queue/drivers/queue-driver.d.ts.map +1 -0
- package/dist/queue/drivers/queue-driver.js +6 -0
- package/dist/queue/drivers/queue-driver.js.map +1 -0
- package/dist/queue/drivers/redis-queue-driver.d.ts +16 -0
- package/dist/queue/drivers/redis-queue-driver.d.ts.map +1 -0
- package/dist/queue/drivers/redis-queue-driver.js +92 -0
- package/dist/queue/drivers/redis-queue-driver.js.map +1 -0
- package/dist/queue/index.d.ts +12 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +12 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/job.d.ts +20 -0
- package/dist/queue/job.d.ts.map +1 -0
- package/dist/queue/job.js +19 -0
- package/dist/queue/job.js.map +1 -0
- package/dist/queue/queue-manager.d.ts +41 -0
- package/dist/queue/queue-manager.d.ts.map +1 -0
- package/dist/queue/queue-manager.js +98 -0
- package/dist/queue/queue-manager.js.map +1 -0
- package/dist/queue/worker.d.ts +57 -0
- package/dist/queue/worker.d.ts.map +1 -0
- package/dist/queue/worker.js +156 -0
- package/dist/queue/worker.js.map +1 -0
- package/dist/redis/index.d.ts +5 -0
- package/dist/redis/index.d.ts.map +1 -0
- package/dist/redis/index.js +5 -0
- package/dist/redis/index.js.map +1 -0
- package/dist/redis/redis-client.d.ts +44 -0
- package/dist/redis/redis-client.d.ts.map +1 -0
- package/dist/redis/redis-client.js +131 -0
- package/dist/redis/redis-client.js.map +1 -0
- package/dist/redis/redis-connection.d.ts +32 -0
- package/dist/redis/redis-connection.d.ts.map +1 -0
- package/dist/redis/redis-connection.js +151 -0
- package/dist/redis/redis-connection.js.map +1 -0
- package/dist/redis/redis-manager.d.ts +28 -0
- package/dist/redis/redis-manager.d.ts.map +1 -0
- package/dist/redis/redis-manager.js +68 -0
- package/dist/redis/redis-manager.js.map +1 -0
- package/dist/redis/resp-parser.d.ts +29 -0
- package/dist/redis/resp-parser.d.ts.map +1 -0
- package/dist/redis/resp-parser.js +113 -0
- package/dist/redis/resp-parser.js.map +1 -0
- package/dist/request.d.ts +122 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +171 -0
- package/dist/request.js.map +1 -0
- package/dist/response.d.ts +208 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +375 -0
- package/dist/response.js.map +1 -0
- package/dist/router.d.ts +146 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +341 -0
- package/dist/router.js.map +1 -0
- package/dist/routing/controller.d.ts +18 -0
- package/dist/routing/controller.d.ts.map +1 -0
- package/dist/routing/controller.js +49 -0
- package/dist/routing/controller.js.map +1 -0
- package/dist/routing/named-middleware.d.ts +27 -0
- package/dist/routing/named-middleware.d.ts.map +1 -0
- package/dist/routing/named-middleware.js +41 -0
- package/dist/routing/named-middleware.js.map +1 -0
- package/dist/routing/route-builder.d.ts +54 -0
- package/dist/routing/route-builder.d.ts.map +1 -0
- package/dist/routing/route-builder.js +95 -0
- package/dist/routing/route-builder.js.map +1 -0
- package/dist/routing/route-group.d.ts +39 -0
- package/dist/routing/route-group.d.ts.map +1 -0
- package/dist/routing/route-group.js +79 -0
- package/dist/routing/route-group.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +85 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +216 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/security/csrf.d.ts +19 -0
- package/dist/security/csrf.d.ts.map +1 -0
- package/dist/security/csrf.js +69 -0
- package/dist/security/csrf.js.map +1 -0
- package/dist/security/hash.d.ts +30 -0
- package/dist/security/hash.d.ts.map +1 -0
- package/dist/security/hash.js +86 -0
- package/dist/security/hash.js.map +1 -0
- package/dist/security/headers.d.ts +56 -0
- package/dist/security/headers.d.ts.map +1 -0
- package/dist/security/headers.js +81 -0
- package/dist/security/headers.js.map +1 -0
- package/dist/security/index.d.ts +13 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +13 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/jwt.d.ts +96 -0
- package/dist/security/jwt.d.ts.map +1 -0
- package/dist/security/jwt.js +308 -0
- package/dist/security/jwt.js.map +1 -0
- package/dist/security/rate-limiter.d.ts +80 -0
- package/dist/security/rate-limiter.d.ts.map +1 -0
- package/dist/security/rate-limiter.js +127 -0
- package/dist/security/rate-limiter.js.map +1 -0
- package/dist/security/totp.d.ts +43 -0
- package/dist/security/totp.d.ts.map +1 -0
- package/dist/security/totp.js +122 -0
- package/dist/security/totp.js.map +1 -0
- package/dist/security/vault.d.ts +28 -0
- package/dist/security/vault.d.ts.map +1 -0
- package/dist/security/vault.js +73 -0
- package/dist/security/vault.js.map +1 -0
- package/dist/session/session-driver.d.ts +53 -0
- package/dist/session/session-driver.d.ts.map +1 -0
- package/dist/session/session-driver.js +132 -0
- package/dist/session/session-driver.js.map +1 -0
- package/dist/session/session-manager.d.ts +100 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +186 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/sse/index.d.ts +2 -0
- package/dist/sse/index.d.ts.map +1 -0
- package/dist/sse/index.js +2 -0
- package/dist/sse/index.js.map +1 -0
- package/dist/sse/sse.d.ts +32 -0
- package/dist/sse/sse.d.ts.map +1 -0
- package/dist/sse/sse.js +87 -0
- package/dist/sse/sse.js.map +1 -0
- package/dist/ssr/engine.d.ts +58 -0
- package/dist/ssr/engine.d.ts.map +1 -0
- package/dist/ssr/engine.js +121 -0
- package/dist/ssr/engine.js.map +1 -0
- package/dist/ssr/index.d.ts +6 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/index.js +6 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/static/static.d.ts +22 -0
- package/dist/static/static.d.ts.map +1 -0
- package/dist/static/static.js +134 -0
- package/dist/static/static.js.map +1 -0
- package/dist/storage/chunked-uploader.d.ts +53 -0
- package/dist/storage/chunked-uploader.d.ts.map +1 -0
- package/dist/storage/chunked-uploader.js +135 -0
- package/dist/storage/chunked-uploader.js.map +1 -0
- package/dist/storage/drivers/local-driver.d.ts +25 -0
- package/dist/storage/drivers/local-driver.d.ts.map +1 -0
- package/dist/storage/drivers/local-driver.js +75 -0
- package/dist/storage/drivers/local-driver.js.map +1 -0
- package/dist/storage/drivers/memory-driver.d.ts +23 -0
- package/dist/storage/drivers/memory-driver.d.ts.map +1 -0
- package/dist/storage/drivers/memory-driver.js +55 -0
- package/dist/storage/drivers/memory-driver.js.map +1 -0
- package/dist/storage/drivers/s3-driver.d.ts +34 -0
- package/dist/storage/drivers/s3-driver.d.ts.map +1 -0
- package/dist/storage/drivers/s3-driver.js +186 -0
- package/dist/storage/drivers/s3-driver.js.map +1 -0
- package/dist/storage/drivers/storage-driver.d.ts +40 -0
- package/dist/storage/drivers/storage-driver.d.ts.map +1 -0
- package/dist/storage/drivers/storage-driver.js +6 -0
- package/dist/storage/drivers/storage-driver.js.map +1 -0
- package/dist/storage/index.d.ts +13 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +13 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/multipart.d.ts +12 -0
- package/dist/storage/multipart.d.ts.map +1 -0
- package/dist/storage/multipart.js +79 -0
- package/dist/storage/multipart.js.map +1 -0
- package/dist/storage/storage-manager.d.ts +48 -0
- package/dist/storage/storage-manager.d.ts.map +1 -0
- package/dist/storage/storage-manager.js +106 -0
- package/dist/storage/storage-manager.js.map +1 -0
- package/dist/storage/uploaded-file.d.ts +40 -0
- package/dist/storage/uploaded-file.d.ts.map +1 -0
- package/dist/storage/uploaded-file.js +73 -0
- package/dist/storage/uploaded-file.js.map +1 -0
- package/dist/swagger/generator.d.ts +44 -0
- package/dist/swagger/generator.d.ts.map +1 -0
- package/dist/swagger/generator.js +117 -0
- package/dist/swagger/generator.js.map +1 -0
- package/dist/swagger/index.d.ts +8 -0
- package/dist/swagger/index.d.ts.map +1 -0
- package/dist/swagger/index.js +8 -0
- package/dist/swagger/index.js.map +1 -0
- package/dist/swagger/swagger.d.ts +9 -0
- package/dist/swagger/swagger.d.ts.map +1 -0
- package/dist/swagger/swagger.js +32 -0
- package/dist/swagger/swagger.js.map +1 -0
- package/dist/swagger/ui.d.ts +11 -0
- package/dist/swagger/ui.d.ts.map +1 -0
- package/dist/swagger/ui.js +93 -0
- package/dist/swagger/ui.js.map +1 -0
- package/dist/tcp/hl7.d.ts +19 -0
- package/dist/tcp/hl7.d.ts.map +1 -0
- package/dist/tcp/hl7.js +42 -0
- package/dist/tcp/hl7.js.map +1 -0
- package/dist/tcp/index.d.ts +4 -0
- package/dist/tcp/index.d.ts.map +1 -0
- package/dist/tcp/index.js +4 -0
- package/dist/tcp/index.js.map +1 -0
- package/dist/tcp/mllp.d.ts +22 -0
- package/dist/tcp/mllp.d.ts.map +1 -0
- package/dist/tcp/mllp.js +44 -0
- package/dist/tcp/mllp.js.map +1 -0
- package/dist/tcp/tcp-server.d.ts +20 -0
- package/dist/tcp/tcp-server.d.ts.map +1 -0
- package/dist/tcp/tcp-server.js +59 -0
- package/dist/tcp/tcp-server.js.map +1 -0
- package/dist/tenancy/index.d.ts +2 -0
- package/dist/tenancy/index.d.ts.map +1 -0
- package/dist/tenancy/index.js +2 -0
- package/dist/tenancy/index.js.map +1 -0
- package/dist/tenancy/tenant.d.ts +22 -0
- package/dist/tenancy/tenant.d.ts.map +1 -0
- package/dist/tenancy/tenant.js +32 -0
- package/dist/tenancy/tenant.js.map +1 -0
- package/dist/testing/test-client.d.ts +36 -0
- package/dist/testing/test-client.d.ts.map +1 -0
- package/dist/testing/test-client.js +128 -0
- package/dist/testing/test-client.js.map +1 -0
- package/dist/validation/index.d.ts +10 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +10 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/rules-validator.d.ts +65 -0
- package/dist/validation/rules-validator.d.ts.map +1 -0
- package/dist/validation/rules-validator.js +301 -0
- package/dist/validation/rules-validator.js.map +1 -0
- package/dist/validation/schema.d.ts +34 -0
- package/dist/validation/schema.d.ts.map +1 -0
- package/dist/validation/schema.js +174 -0
- package/dist/validation/schema.js.map +1 -0
- package/dist/validation/vine.d.ts +23 -0
- package/dist/validation/vine.d.ts.map +1 -0
- package/dist/validation/vine.js +97 -0
- package/dist/validation/vine.js.map +1 -0
- package/dist/views/view.d.ts +51 -0
- package/dist/views/view.d.ts.map +1 -0
- package/dist/views/view.js +91 -0
- package/dist/views/view.js.map +1 -0
- package/dist/views/vite.d.ts +19 -0
- package/dist/views/vite.d.ts.map +1 -0
- package/dist/views/vite.js +60 -0
- package/dist/views/vite.js.map +1 -0
- package/dist/webhook/index.d.ts +2 -0
- package/dist/webhook/index.d.ts.map +1 -0
- package/dist/webhook/index.js +2 -0
- package/dist/webhook/index.js.map +1 -0
- package/dist/webhook/webhook.d.ts +30 -0
- package/dist/webhook/webhook.d.ts.map +1 -0
- package/dist/webhook/webhook.js +124 -0
- package/dist/webhook/webhook.js.map +1 -0
- package/dist/ws/broadcaster.d.ts +43 -0
- package/dist/ws/broadcaster.d.ts.map +1 -0
- package/dist/ws/broadcaster.js +81 -0
- package/dist/ws/broadcaster.js.map +1 -0
- package/dist/ws/channel.d.ts +23 -0
- package/dist/ws/channel.d.ts.map +1 -0
- package/dist/ws/channel.js +27 -0
- package/dist/ws/channel.js.map +1 -0
- package/dist/ws/index.d.ts +5 -0
- package/dist/ws/index.d.ts.map +1 -0
- package/dist/ws/index.js +5 -0
- package/dist/ws/index.js.map +1 -0
- package/dist/ws/websocket-hub.d.ts +52 -0
- package/dist/ws/websocket-hub.d.ts.map +1 -0
- package/dist/ws/websocket-hub.js +187 -0
- package/dist/ws/websocket-hub.js.map +1 -0
- package/dist/ws/websocket.d.ts +43 -0
- package/dist/ws/websocket.d.ts.map +1 -0
- package/dist/ws/websocket.js +214 -0
- package/dist/ws/websocket.js.map +1 -0
- package/package.json +141 -0
|
@@ -0,0 +1,1760 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ecommerce-erp.ts
|
|
3
|
+
* @description Official Omnichannel E-Commerce & Nursery ERP full-stack starter template generator.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
function writeFileWithDir(filePath, content) {
|
|
8
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
9
|
+
fs.writeFileSync(filePath, content.trimStart(), 'utf-8');
|
|
10
|
+
}
|
|
11
|
+
export function scaffoldEcommerceErp(dir, projectName) {
|
|
12
|
+
const createdFiles = [];
|
|
13
|
+
const addFile = (relPath, content) => {
|
|
14
|
+
const fullPath = path.join(dir, relPath);
|
|
15
|
+
writeFileWithDir(fullPath, content);
|
|
16
|
+
createdFiles.push(fullPath);
|
|
17
|
+
};
|
|
18
|
+
// 1. package.json
|
|
19
|
+
addFile('package.json', `{
|
|
20
|
+
"name": "${projectName}",
|
|
21
|
+
"version": "1.0.0",
|
|
22
|
+
"description": "Omnichannel E-Commerce & Nursery ERP Backend powered by AeroJS",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "server.ts",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"dev": "concurrently \\"tsx watch server.ts\\" \\"vite\\"",
|
|
27
|
+
"server": "tsx watch server.ts",
|
|
28
|
+
"build": "vite build && tsc",
|
|
29
|
+
"start": "node dist/server.js",
|
|
30
|
+
"cli": "tsx cli.ts",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"db:seed": "tsx database/seed.ts"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@inertiajs/vue3": "^1.2.0",
|
|
36
|
+
"aerojs": "file:../AeroJS",
|
|
37
|
+
"knex": "^3.3.0",
|
|
38
|
+
"mysql2": "^3.11.0",
|
|
39
|
+
"vue": "^3.4.38"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"@vitejs/plugin-vue": "^5.1.2",
|
|
44
|
+
"concurrently": "^8.2.2",
|
|
45
|
+
"tsx": "^4.19.0",
|
|
46
|
+
"typescript": "^5.6.0",
|
|
47
|
+
"vite": "^5.4.2",
|
|
48
|
+
"vitest": "^2.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
`);
|
|
52
|
+
// 2. tsconfig.json
|
|
53
|
+
addFile('tsconfig.json', `{
|
|
54
|
+
"compilerOptions": {
|
|
55
|
+
"target": "ES2022",
|
|
56
|
+
"module": "NodeNext",
|
|
57
|
+
"moduleResolution": "NodeNext",
|
|
58
|
+
"esModuleInterop": true,
|
|
59
|
+
"experimentalDecorators": true,
|
|
60
|
+
"emitDecoratorMetadata": true,
|
|
61
|
+
"strict": true,
|
|
62
|
+
"skipLibCheck": true,
|
|
63
|
+
"jsx": "preserve",
|
|
64
|
+
"outDir": "./dist"
|
|
65
|
+
},
|
|
66
|
+
"include": [
|
|
67
|
+
"app/**/*",
|
|
68
|
+
"config/**/*",
|
|
69
|
+
"routes/**/*",
|
|
70
|
+
"database/**/*",
|
|
71
|
+
"resources/**/*",
|
|
72
|
+
"server.ts",
|
|
73
|
+
"cli.ts",
|
|
74
|
+
"tests/**/*"
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
`);
|
|
78
|
+
// 3. vite.config.ts
|
|
79
|
+
addFile('vite.config.ts', `import { defineConfig } from 'vite';
|
|
80
|
+
import vue from '@vitejs/plugin-vue';
|
|
81
|
+
import path from 'path';
|
|
82
|
+
|
|
83
|
+
export default defineConfig({
|
|
84
|
+
plugins: [vue()],
|
|
85
|
+
publicDir: false,
|
|
86
|
+
resolve: {
|
|
87
|
+
alias: {
|
|
88
|
+
'@': path.resolve(__dirname, 'resources/js'),
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
server: {
|
|
92
|
+
cors: true,
|
|
93
|
+
port: 5173,
|
|
94
|
+
strictPort: true,
|
|
95
|
+
},
|
|
96
|
+
build: {
|
|
97
|
+
outDir: 'public/build',
|
|
98
|
+
manifest: true,
|
|
99
|
+
rollupOptions: {
|
|
100
|
+
input: 'resources/js/app.ts',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
`);
|
|
105
|
+
// 4. .env & .env.example
|
|
106
|
+
const envContent = `PORT=3000
|
|
107
|
+
NODE_ENV=development
|
|
108
|
+
APP_NAME=NurseryERP
|
|
109
|
+
APP_KEY=nursery-erp-secret-key-32chars-min-length-123456
|
|
110
|
+
|
|
111
|
+
# Database
|
|
112
|
+
DB_CONNECTION=mysql
|
|
113
|
+
DB_HOST=127.0.0.1
|
|
114
|
+
DB_PORT=3306
|
|
115
|
+
DB_USER=root
|
|
116
|
+
DB_PASSWORD=
|
|
117
|
+
DB_DATABASE=nursery_erp_db
|
|
118
|
+
DB_POOL_MIN=2
|
|
119
|
+
DB_POOL_MAX=20
|
|
120
|
+
|
|
121
|
+
# JWT Auth
|
|
122
|
+
JWT_SECRET=nursery-erp-jwt-secret-key-for-admin-and-staff
|
|
123
|
+
|
|
124
|
+
# 3PL Courier Credentials
|
|
125
|
+
STEADFAST_API_KEY=
|
|
126
|
+
STEADFAST_SECRET_KEY=
|
|
127
|
+
PATHAO_CLIENT_ID=
|
|
128
|
+
PATHAO_CLIENT_SECRET=
|
|
129
|
+
PATHAO_STORE_ID=
|
|
130
|
+
`;
|
|
131
|
+
addFile('.env', envContent);
|
|
132
|
+
addFile('.env.example', envContent);
|
|
133
|
+
// 5. Config files
|
|
134
|
+
addFile('config/app.ts', `export const appConfig = {
|
|
135
|
+
name: process.env.APP_NAME || 'NurseryERP',
|
|
136
|
+
port: parseInt(process.env.PORT || '3000', 10),
|
|
137
|
+
env: process.env.NODE_ENV || 'development',
|
|
138
|
+
debug: process.env.NODE_ENV !== 'production',
|
|
139
|
+
key: process.env.APP_KEY || 'default-secret-key-32chars-min-length-123',
|
|
140
|
+
jwtSecret: process.env.JWT_SECRET || 'nursery-erp-jwt-secret-key-for-admin-and-staff',
|
|
141
|
+
};
|
|
142
|
+
`);
|
|
143
|
+
addFile('config/database.ts', `export const databaseConfig = {
|
|
144
|
+
default: (process.env.DB_CONNECTION || 'mysql').toLowerCase(),
|
|
145
|
+
connections: {
|
|
146
|
+
mysql: {
|
|
147
|
+
client: 'mysql2',
|
|
148
|
+
host: process.env.DB_HOST || '127.0.0.1',
|
|
149
|
+
port: parseInt(process.env.DB_PORT || '3306', 10),
|
|
150
|
+
user: process.env.DB_USER || 'root',
|
|
151
|
+
password: process.env.DB_PASSWORD || '',
|
|
152
|
+
database: process.env.DB_DATABASE || 'nursery_erp_db',
|
|
153
|
+
},
|
|
154
|
+
memory: {
|
|
155
|
+
client: 'memory',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
pool: {
|
|
159
|
+
min: parseInt(process.env.DB_POOL_MIN || '2', 10),
|
|
160
|
+
max: parseInt(process.env.DB_POOL_MAX || '20', 10),
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
`);
|
|
164
|
+
addFile('config/security.ts', `export const securityConfig = {
|
|
165
|
+
jwt: {
|
|
166
|
+
secret: process.env.JWT_SECRET || 'nursery-erp-jwt-secret-key-for-admin-and-staff',
|
|
167
|
+
expiresIn: '7d',
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
`);
|
|
171
|
+
// 6. Database Connection & Seed
|
|
172
|
+
addFile('database/connection.ts', `import knex, { type Knex } from 'knex';
|
|
173
|
+
import { loadEnv, useKnex, DB, Database } from 'aerojs';
|
|
174
|
+
import { databaseConfig } from '../config/database.js';
|
|
175
|
+
|
|
176
|
+
loadEnv();
|
|
177
|
+
|
|
178
|
+
let knexInstance: Knex | null = null;
|
|
179
|
+
|
|
180
|
+
export function getKnex(): Knex {
|
|
181
|
+
if (!knexInstance) {
|
|
182
|
+
knexInstance = knex({
|
|
183
|
+
client: 'mysql2',
|
|
184
|
+
connection: {
|
|
185
|
+
host: databaseConfig.connections.mysql.host,
|
|
186
|
+
port: databaseConfig.connections.mysql.port,
|
|
187
|
+
user: databaseConfig.connections.mysql.user,
|
|
188
|
+
password: databaseConfig.connections.mysql.password,
|
|
189
|
+
database: databaseConfig.connections.mysql.database,
|
|
190
|
+
},
|
|
191
|
+
pool: {
|
|
192
|
+
min: databaseConfig.pool.min,
|
|
193
|
+
max: databaseConfig.pool.max,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
useKnex(knexInstance);
|
|
198
|
+
}
|
|
199
|
+
return knexInstance;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function initDatabase(): Promise<Knex> {
|
|
203
|
+
const instance = getKnex();
|
|
204
|
+
await instance.raw('SELECT 1 + 1 AS result');
|
|
205
|
+
return instance;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function closeDatabase(): Promise<void> {
|
|
209
|
+
if (knexInstance) {
|
|
210
|
+
await knexInstance.destroy();
|
|
211
|
+
knexInstance = null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export { DB, Database };
|
|
216
|
+
`);
|
|
217
|
+
addFile('database/seed.ts', `import { initDatabase, DB, closeDatabase } from './connection.js';
|
|
218
|
+
|
|
219
|
+
export async function seed() {
|
|
220
|
+
await initDatabase();
|
|
221
|
+
console.log('Seeding default ERP Chart of Accounts...');
|
|
222
|
+
|
|
223
|
+
const existingGroups = await DB.table('tbl_ledger_group').count();
|
|
224
|
+
if (Number(existingGroups) === 0) {
|
|
225
|
+
await DB.table('tbl_ledger_group').insert([
|
|
226
|
+
{ h_name: 'Current Assets', code: '1000', type: 'Asset', ed: 1 },
|
|
227
|
+
{ h_name: 'Current Liabilities', code: '2000', type: 'Liability', ed: 1 },
|
|
228
|
+
{ h_name: 'Operating Income', code: '3000', type: 'Income', ed: 1 },
|
|
229
|
+
{ h_name: 'Operating Expense', code: '4000', type: 'Expense', ed: 1 },
|
|
230
|
+
{ h_name: 'Owner Equity', code: '5000', type: 'Equity', ed: 1 },
|
|
231
|
+
]);
|
|
232
|
+
console.log('Inserted default ledger groups.');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const existingLedgers = await DB.table('tbl_ledger').count();
|
|
236
|
+
if (Number(existingLedgers) === 0) {
|
|
237
|
+
await DB.table('tbl_ledger').insert([
|
|
238
|
+
{ h_id: 1, l_name: 'Cash in Hand', l_balance: 0, c_balance: 0, ed: 1 },
|
|
239
|
+
{ h_id: 1, l_name: 'Bank Account (BRAC)', l_balance: 0, c_balance: 0, ed: 1 },
|
|
240
|
+
{ h_id: 1, l_name: 'Inventory Asset Account', l_balance: 0, c_balance: 0, ed: 1 },
|
|
241
|
+
{ h_id: 1, l_name: 'Courier Clearing Account', l_balance: 0, c_balance: 0, ed: 1 },
|
|
242
|
+
{ h_id: 3, l_name: 'Sales Revenue', l_balance: 0, c_balance: 0, ed: 1 },
|
|
243
|
+
{ h_id: 4, l_name: 'Cost of Goods Sold (COGS)', l_balance: 0, c_balance: 0, ed: 1 },
|
|
244
|
+
{ h_id: 4, l_name: 'Delivery & Shipping Expense', l_balance: 0, c_balance: 0, ed: 1 },
|
|
245
|
+
]);
|
|
246
|
+
console.log('Inserted default ledgers.');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log('Seeding completed successfully!');
|
|
250
|
+
await closeDatabase();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (process.argv[1]?.includes('seed.ts')) {
|
|
254
|
+
seed().catch(console.error);
|
|
255
|
+
}
|
|
256
|
+
`);
|
|
257
|
+
// 7. Active Record Models
|
|
258
|
+
addFile('app/models/Product.ts', `import { Model } from 'aerojs';
|
|
259
|
+
import { ProductLandingPage } from './ProductLandingPage.js';
|
|
260
|
+
import { OnlineOrderItem } from './OnlineOrderItem.js';
|
|
261
|
+
import { ProductTransaction } from './ProductTransaction.js';
|
|
262
|
+
import { SaleDetail } from './SaleDetail.js';
|
|
263
|
+
|
|
264
|
+
export class Product extends Model {
|
|
265
|
+
public static override table = 'tbl_product';
|
|
266
|
+
public static override primaryKey = 'product_id';
|
|
267
|
+
public static override timestamps = false;
|
|
268
|
+
|
|
269
|
+
public static override fillable = [
|
|
270
|
+
'parent_id', 'sg_id', 'web_category_id', 'sc_id', 'su_id', 'part_no',
|
|
271
|
+
'product_name', 'slug', 'image', 'description', 'scientific_name',
|
|
272
|
+
'plant_type', 'sunlight', 'watering', 'fruiting_time', 'pot_size',
|
|
273
|
+
'care_tips', 'is_featured', 'is_online_visible', 'is_plant', 'base_uom',
|
|
274
|
+
'sub_uom', 'conversion_factor', 'pak_size', 'in_rate', 'bata_rate',
|
|
275
|
+
'per_rate', 'cost_rate', 'cgp_rate', 'sale_rate', 'tp_rate', 'dp_rate',
|
|
276
|
+
'mrp_rate', 'w_id', 'o_stock', 'o_rate', 'o_amount', 'o_date',
|
|
277
|
+
'c_stock', 'avg_rate', 'entry_time', 'product_type', 'status'
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
public landingPage() {
|
|
281
|
+
return this.hasOne(ProductLandingPage, 'product_id', 'product_id');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
public orderItems() {
|
|
285
|
+
return this.hasMany(OnlineOrderItem, 'product_id', 'product_id');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public transactions() {
|
|
289
|
+
return this.hasMany(ProductTransaction, 'pb_id', 'product_id');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
public saleDetails() {
|
|
293
|
+
return this.hasMany(SaleDetail, 'product_id', 'product_id');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
`);
|
|
297
|
+
addFile('app/models/ProductLandingPage.ts', `import { Model } from 'aerojs';
|
|
298
|
+
import { Product } from './Product.js';
|
|
299
|
+
|
|
300
|
+
export class ProductLandingPage extends Model {
|
|
301
|
+
public static override table = 'tbl_product_landing_pages';
|
|
302
|
+
public static override primaryKey = 'id';
|
|
303
|
+
public static override timestamps = true;
|
|
304
|
+
|
|
305
|
+
public static override fillable = [
|
|
306
|
+
'product_id', 'slug', 'headline', 'subheadline', 'badge_text',
|
|
307
|
+
'video_url', 'banner_image', 'gallery_images_json', 'features_json',
|
|
308
|
+
'packages_json', 'guarantee_text', 'reviews_json', 'faq_json',
|
|
309
|
+
'fb_pixel_id', 'seo_meta_title', 'seo_meta_description',
|
|
310
|
+
'seo_meta_keywords', 'og_image', 'custom_head_scripts', 'views_count',
|
|
311
|
+
'orders_count', 'is_active'
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
public product() {
|
|
315
|
+
return this.belongsTo(Product, 'product_id', 'product_id');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
`);
|
|
319
|
+
addFile('app/models/OnlineOrderItem.ts', `import { Model } from 'aerojs';
|
|
320
|
+
import { OnlineOrder } from './OnlineOrder.js';
|
|
321
|
+
import { Product } from './Product.js';
|
|
322
|
+
|
|
323
|
+
export class OnlineOrderItem extends Model {
|
|
324
|
+
public static override table = 'tbl_online_order_items';
|
|
325
|
+
public static override primaryKey = 'id';
|
|
326
|
+
public static override timestamps = false;
|
|
327
|
+
|
|
328
|
+
public static override fillable = [
|
|
329
|
+
'order_id', 'product_id', 'product_name', 'unit_price', 'qty',
|
|
330
|
+
'subtotal', 'image_url', 'created_at'
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
public order() {
|
|
334
|
+
return this.belongsTo(OnlineOrder, 'order_id', 'id');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
public product() {
|
|
338
|
+
return this.belongsTo(Product, 'product_id', 'product_id');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
`);
|
|
342
|
+
addFile('app/models/OnlineOrder.ts', `import { Model } from 'aerojs';
|
|
343
|
+
import { OnlineOrderItem } from './OnlineOrderItem.js';
|
|
344
|
+
import { SaleHead } from './SaleHead.js';
|
|
345
|
+
|
|
346
|
+
export class OnlineOrder extends Model {
|
|
347
|
+
public static override table = 'tbl_online_orders';
|
|
348
|
+
public static override primaryKey = 'id';
|
|
349
|
+
public static override timestamps = true;
|
|
350
|
+
|
|
351
|
+
public static override fillable = [
|
|
352
|
+
'order_no', 'customer_name', 'customer_phone', 'customer_address',
|
|
353
|
+
'delivery_district', 'total_items_count', 'total_amount', 'payment_method',
|
|
354
|
+
'courier_name', 'consignment_no', 'courier_tracking_id', 'courier_status',
|
|
355
|
+
'courier_delivery_fee', 'pathao_city_id', 'pathao_zone_id', 'pathao_area_id',
|
|
356
|
+
'order_notes', 'delivery_charge', 'status', 'sale_id'
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
public items() {
|
|
360
|
+
return this.hasMany(OnlineOrderItem, 'order_id', 'id');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
public saleHead() {
|
|
364
|
+
return this.belongsTo(SaleHead, 'sale_id', 'id');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
`);
|
|
368
|
+
addFile('app/models/SaleHead.ts', `import { Model } from 'aerojs';
|
|
369
|
+
import { SaleDetail } from './SaleDetail.js';
|
|
370
|
+
import { Ledger } from './Ledger.js';
|
|
371
|
+
import { OnlineOrder } from './OnlineOrder.js';
|
|
372
|
+
import { DeliveryChallan } from './DeliveryChallan.js';
|
|
373
|
+
|
|
374
|
+
export class SaleHead extends Model {
|
|
375
|
+
public static override table = 'tbl_sale_head';
|
|
376
|
+
public static override primaryKey = 'id';
|
|
377
|
+
public static override timestamps = false;
|
|
378
|
+
|
|
379
|
+
public static override fillable = [
|
|
380
|
+
'vh_no', 'bill_no', 'vh_date', 'l_id', 's_id', 'c_id', 'w_id',
|
|
381
|
+
'qty', 'total_amount', 'other_cost', 'discount', 'cash_amount',
|
|
382
|
+
'net_amount', 'prev_balance', 'narration', 'sale_type', 'entry_time'
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
public details() {
|
|
386
|
+
return this.hasMany(SaleDetail, 'vh_id', 'id');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
public ledger() {
|
|
390
|
+
return this.belongsTo(Ledger, 'l_id', 'l_id');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
public order() {
|
|
394
|
+
return this.hasOne(OnlineOrder, 'sale_id', 'id');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
public challan() {
|
|
398
|
+
return this.hasOne(DeliveryChallan, 'sale_id', 'id');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
`);
|
|
402
|
+
addFile('app/models/SaleDetail.ts', `import { Model } from 'aerojs';
|
|
403
|
+
import { SaleHead } from './SaleHead.js';
|
|
404
|
+
import { Product } from './Product.js';
|
|
405
|
+
|
|
406
|
+
export class SaleDetail extends Model {
|
|
407
|
+
public static override table = 'tbl_sale_detail';
|
|
408
|
+
public static override primaryKey = 'id';
|
|
409
|
+
public static override timestamps = false;
|
|
410
|
+
|
|
411
|
+
public static override fillable = [
|
|
412
|
+
'vh_id', 'vh_date', 'product_id', 'w_id', 'in_rate', 'bata_rate',
|
|
413
|
+
'qty', 'cost_rate', 'cgp_rate', 'amount', 'entry_time', 'expiry_date'
|
|
414
|
+
];
|
|
415
|
+
|
|
416
|
+
public saleHead() {
|
|
417
|
+
return this.belongsTo(SaleHead, 'vh_id', 'id');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
public product() {
|
|
421
|
+
return this.belongsTo(Product, 'product_id', 'product_id');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
`);
|
|
425
|
+
addFile('app/models/DeliveryChallan.ts', `import { Model } from 'aerojs';
|
|
426
|
+
import { SaleHead } from './SaleHead.js';
|
|
427
|
+
|
|
428
|
+
export class DeliveryChallan extends Model {
|
|
429
|
+
public static override table = 'tbl_delivery_challans';
|
|
430
|
+
public static override primaryKey = 'id';
|
|
431
|
+
public static override timestamps = false;
|
|
432
|
+
|
|
433
|
+
public static override fillable = [
|
|
434
|
+
'challan_no', 'gate_pass_no', 'sale_id', 'customer_id', 'customer_name',
|
|
435
|
+
'customer_phone', 'delivery_address', 'vehicle_type', 'vehicle_no',
|
|
436
|
+
'driver_name', 'driver_phone', 'truck_fare', 'fare_paid_by',
|
|
437
|
+
'total_plants_qty', 'delivery_date', 'delivery_time', 'notes',
|
|
438
|
+
'status', 'created_by', 'created_at'
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
public saleHead() {
|
|
442
|
+
return this.belongsTo(SaleHead, 'sale_id', 'id');
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
`);
|
|
446
|
+
addFile('app/models/ProductTransaction.ts', `import { Model } from 'aerojs';
|
|
447
|
+
import { Product } from './Product.js';
|
|
448
|
+
|
|
449
|
+
export class ProductTransaction extends Model {
|
|
450
|
+
public static override table = 'tbl_product_transactions';
|
|
451
|
+
public static override primaryKey = 'id';
|
|
452
|
+
public static override timestamps = false;
|
|
453
|
+
|
|
454
|
+
public static override fillable = [
|
|
455
|
+
'pb_id', 'w_id', 'vh_id', 'vh_no', 'vh_date', 'type', 'qty',
|
|
456
|
+
'rate', 'value', 'entrytime', 'expiry_date'
|
|
457
|
+
];
|
|
458
|
+
|
|
459
|
+
public product() {
|
|
460
|
+
return this.belongsTo(Product, 'pb_id', 'product_id');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
`);
|
|
464
|
+
addFile('app/models/LedgerGroup.ts', `import { Model } from 'aerojs';
|
|
465
|
+
import { Ledger } from './Ledger.js';
|
|
466
|
+
|
|
467
|
+
export class LedgerGroup extends Model {
|
|
468
|
+
public static override table = 'tbl_ledger_group';
|
|
469
|
+
public static override primaryKey = 'h_id';
|
|
470
|
+
public static override timestamps = false;
|
|
471
|
+
|
|
472
|
+
public static override fillable = [
|
|
473
|
+
'h_name', 'code', 'parent_id', 'type', 'ed', 'entry_time'
|
|
474
|
+
];
|
|
475
|
+
|
|
476
|
+
public ledgers() {
|
|
477
|
+
return this.hasMany(Ledger, 'h_id', 'h_id');
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
`);
|
|
481
|
+
addFile('app/models/Ledger.ts', `import { Model } from 'aerojs';
|
|
482
|
+
import { LedgerGroup } from './LedgerGroup.js';
|
|
483
|
+
import { LedgerTransaction } from './LedgerTransaction.js';
|
|
484
|
+
|
|
485
|
+
export class Ledger extends Model {
|
|
486
|
+
public static override table = 'tbl_ledger';
|
|
487
|
+
public static override primaryKey = 'l_id';
|
|
488
|
+
public static override timestamps = false;
|
|
489
|
+
|
|
490
|
+
public static override fillable = [
|
|
491
|
+
'h_id', 'l_name', 'l_address', 'l_narration', 'l_mobile', 'l_pro_name',
|
|
492
|
+
'l_pro_mobile', 'l_balance', 'c_balance', 'credit_limit', 'foreign_currency',
|
|
493
|
+
'country', 'route_id', 'salesman_id', 'open_date', 'ed', 'entry_time'
|
|
494
|
+
];
|
|
495
|
+
|
|
496
|
+
public group() {
|
|
497
|
+
return this.belongsTo(LedgerGroup, 'h_id', 'h_id');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
public transactions() {
|
|
501
|
+
return this.hasMany(LedgerTransaction, 'l_id', 'l_id');
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
`);
|
|
505
|
+
addFile('app/models/LedgerTransaction.ts', `import { Model } from 'aerojs';
|
|
506
|
+
import { Ledger } from './Ledger.js';
|
|
507
|
+
|
|
508
|
+
export class LedgerTransaction extends Model {
|
|
509
|
+
public static override table = 'tbl_ledger_transactions';
|
|
510
|
+
public static override primaryKey = 'id';
|
|
511
|
+
public static override timestamps = false;
|
|
512
|
+
|
|
513
|
+
public static override fillable = [
|
|
514
|
+
'vh_id', 'vh_no', 'vh_date', 'l_id', 'for_l_id', 'type', 'notes',
|
|
515
|
+
'dr_amt', 'cr_amt', 'amount', 'entrytime'
|
|
516
|
+
];
|
|
517
|
+
|
|
518
|
+
public ledger() {
|
|
519
|
+
return this.belongsTo(Ledger, 'l_id', 'l_id');
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
public counterLedger() {
|
|
523
|
+
return this.belongsTo(Ledger, 'for_l_id', 'l_id');
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
`);
|
|
527
|
+
addFile('app/models/Employee.ts', `import { Model } from 'aerojs';
|
|
528
|
+
import { Ledger } from './Ledger.js';
|
|
529
|
+
|
|
530
|
+
export class Employee extends Model {
|
|
531
|
+
public static override table = 'tbl_employees';
|
|
532
|
+
public static override primaryKey = 'id';
|
|
533
|
+
public static override timestamps = false;
|
|
534
|
+
|
|
535
|
+
public static override fillable = [
|
|
536
|
+
'l_id', 'emp_id', 'name', 'designation', 'department', 'salary_type',
|
|
537
|
+
'basic_salary', 'daily_rate', 'morning_rate', 'afternoon_rate',
|
|
538
|
+
'join_date', 'status', 'created_at'
|
|
539
|
+
];
|
|
540
|
+
|
|
541
|
+
public ledger() {
|
|
542
|
+
return this.belongsTo(Ledger, 'l_id', 'l_id');
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
`);
|
|
546
|
+
addFile('app/models/index.ts', `export * from './Product.js';
|
|
547
|
+
export * from './ProductLandingPage.js';
|
|
548
|
+
export * from './OnlineOrder.js';
|
|
549
|
+
export * from './OnlineOrderItem.js';
|
|
550
|
+
export * from './ProductTransaction.js';
|
|
551
|
+
export * from './DeliveryChallan.js';
|
|
552
|
+
export * from './SaleHead.js';
|
|
553
|
+
export * from './SaleDetail.js';
|
|
554
|
+
export * from './LedgerGroup.js';
|
|
555
|
+
export * from './Ledger.js';
|
|
556
|
+
export * from './LedgerTransaction.js';
|
|
557
|
+
export * from './Employee.js';
|
|
558
|
+
`);
|
|
559
|
+
// 8. Services
|
|
560
|
+
addFile('app/services/ProductService.ts', `import { Product } from '../models/Product.js';
|
|
561
|
+
import { ProductLandingPage } from '../models/ProductLandingPage.js';
|
|
562
|
+
|
|
563
|
+
export class ProductService {
|
|
564
|
+
public async paginate(page = 1, limit = 20, search = '') {
|
|
565
|
+
let query = Product.query();
|
|
566
|
+
if (search) {
|
|
567
|
+
query = query.where('product_name', 'LIKE', \`%\${search}%\`);
|
|
568
|
+
}
|
|
569
|
+
return query.paginate(page, limit);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
public async findById(id: number | string) {
|
|
573
|
+
return Product.find(id);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
public async findBySlug(slug: string) {
|
|
577
|
+
const landing = await ProductLandingPage.where('slug', slug).first();
|
|
578
|
+
if (landing) {
|
|
579
|
+
const product = await Product.find(landing.get('product_id'));
|
|
580
|
+
return { landing, product };
|
|
581
|
+
}
|
|
582
|
+
const product = await Product.where('slug', slug).first();
|
|
583
|
+
return { product, landing: null };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
public async checkAtpStock(productId: number | string): Promise<number> {
|
|
587
|
+
const product = await Product.find(productId);
|
|
588
|
+
if (!product) return 0;
|
|
589
|
+
return Number(product.get('c_stock') || 0);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
`);
|
|
593
|
+
addFile('app/services/OrderService.ts', `import { DB } from '../../database/connection.js';
|
|
594
|
+
import { OnlineOrder } from '../models/OnlineOrder.js';
|
|
595
|
+
import { OnlineOrderItem } from '../models/OnlineOrderItem.js';
|
|
596
|
+
import { Product } from '../models/Product.js';
|
|
597
|
+
import { SaleHead } from '../models/SaleHead.js';
|
|
598
|
+
import { SaleDetail } from '../models/SaleDetail.js';
|
|
599
|
+
import { DeliveryChallan } from '../models/DeliveryChallan.js';
|
|
600
|
+
import { LedgerService } from './LedgerService.js';
|
|
601
|
+
|
|
602
|
+
export class OrderService {
|
|
603
|
+
private ledgerService = new LedgerService();
|
|
604
|
+
|
|
605
|
+
public async createOrder(data: {
|
|
606
|
+
customer_name: string;
|
|
607
|
+
customer_phone: string;
|
|
608
|
+
customer_address: string;
|
|
609
|
+
delivery_district: string;
|
|
610
|
+
payment_method?: string;
|
|
611
|
+
order_notes?: string;
|
|
612
|
+
delivery_charge?: number;
|
|
613
|
+
items: Array<{ product_id: number; qty: number; unit_price?: number }>;
|
|
614
|
+
}) {
|
|
615
|
+
return DB.transaction(async (trx) => {
|
|
616
|
+
const orderNo = 'ORD-' + Date.now();
|
|
617
|
+
let totalAmount = 0;
|
|
618
|
+
let totalItems = 0;
|
|
619
|
+
const orderItemsToInsert: any[] = [];
|
|
620
|
+
|
|
621
|
+
for (const item of data.items) {
|
|
622
|
+
const product = await Product.find(item.product_id, trx);
|
|
623
|
+
if (!product) throw new Error(\`Product ID \${item.product_id} not found\`);
|
|
624
|
+
const price = item.unit_price !== undefined ? item.unit_price : Number(product.get('sale_rate') || 0);
|
|
625
|
+
const subtotal = price * item.qty;
|
|
626
|
+
totalAmount += subtotal;
|
|
627
|
+
totalItems += item.qty;
|
|
628
|
+
|
|
629
|
+
orderItemsToInsert.push({
|
|
630
|
+
product_id: product.get('product_id'),
|
|
631
|
+
product_name: product.get('product_name'),
|
|
632
|
+
unit_price: price,
|
|
633
|
+
qty: item.qty,
|
|
634
|
+
subtotal,
|
|
635
|
+
image_url: product.get('image'),
|
|
636
|
+
created_at: new Date(),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const deliveryCharge = data.delivery_charge || 120;
|
|
641
|
+
const grandTotal = totalAmount + deliveryCharge;
|
|
642
|
+
|
|
643
|
+
const orderResult = await trx.execute(
|
|
644
|
+
\`INSERT INTO tbl_online_orders
|
|
645
|
+
(order_no, customer_name, customer_phone, customer_address, delivery_district, total_items_count, total_amount, payment_method, delivery_charge, status, created_at, updated_at)
|
|
646
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NOW(), NOW())\`,
|
|
647
|
+
[orderNo, data.customer_name, data.customer_phone, data.customer_address, data.delivery_district, totalItems, grandTotal, data.payment_method || 'cod', deliveryCharge]
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
const orderId = orderResult.insertId;
|
|
651
|
+
|
|
652
|
+
for (const oi of orderItemsToInsert) {
|
|
653
|
+
await trx.execute(
|
|
654
|
+
\`INSERT INTO tbl_online_order_items (order_id, product_id, product_name, unit_price, qty, subtotal, image_url, created_at)
|
|
655
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)\`,
|
|
656
|
+
[orderId, oi.product_id, oi.product_name, oi.unit_price, oi.qty, oi.subtotal, oi.image_url, oi.created_at]
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return { id: orderId, order_no: orderNo, total_amount: grandTotal, status: 'pending' };
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
public async confirmOrder(orderId: number | string) {
|
|
665
|
+
return DB.transaction(async (trx) => {
|
|
666
|
+
const order = await OnlineOrder.findOrFail(orderId, trx);
|
|
667
|
+
if (order.get('status') === 'confirmed' || order.get('status') === 'delivered') {
|
|
668
|
+
return order;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const items = await OnlineOrderItem.where('order_id', orderId).get(trx);
|
|
672
|
+
const vhNo = 'INV-' + Date.now();
|
|
673
|
+
const vhDate = new Date().toISOString().slice(0, 10);
|
|
674
|
+
const netAmount = Number(order.get('total_amount') || 0);
|
|
675
|
+
|
|
676
|
+
// 1. Create Sale Voucher (tbl_sale_head)
|
|
677
|
+
const saleResult = await trx.execute(
|
|
678
|
+
\`INSERT INTO tbl_sale_head (vh_no, bill_no, vh_date, l_id, qty, total_amount, net_amount, narration, sale_type, entry_time)
|
|
679
|
+
VALUES (?, ?, ?, 1, ?, ?, ?, ?, 1, NOW())\`,
|
|
680
|
+
[vhNo, order.get('order_no'), vhDate, order.get('total_items_count'), netAmount, netAmount, \`Sale for Online Order \${order.get('order_no')}\`]
|
|
681
|
+
);
|
|
682
|
+
const saleId = saleResult.insertId;
|
|
683
|
+
|
|
684
|
+
// 2. Insert Sale Details & Deduct Stock
|
|
685
|
+
for (const item of items) {
|
|
686
|
+
await trx.execute(
|
|
687
|
+
\`INSERT INTO tbl_sale_detail (vh_id, vh_date, product_id, qty, amount, entry_time)
|
|
688
|
+
VALUES (?, ?, ?, ?, ?, NOW())\`,
|
|
689
|
+
[saleId, vhDate, item.get('product_id'), item.get('qty'), item.get('subtotal')]
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
// Deduct inventory
|
|
693
|
+
await trx.execute(
|
|
694
|
+
\`UPDATE tbl_product SET c_stock = c_stock - ? WHERE product_id = ?\`,
|
|
695
|
+
[item.get('qty'), item.get('product_id')]
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
// Record stock movement transaction
|
|
699
|
+
await trx.execute(
|
|
700
|
+
\`INSERT INTO tbl_product_transactions (pb_id, vh_id, vh_no, vh_date, type, qty, value, entrytime)
|
|
701
|
+
VALUES (?, ?, ?, ?, 'sale', ?, ?, NOW())\`,
|
|
702
|
+
[item.get('product_id'), saleId, vhNo, vhDate, item.get('qty'), item.get('subtotal')]
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// 3. Create Delivery Challan
|
|
707
|
+
const challanNo = 'CHL-' + Date.now();
|
|
708
|
+
await trx.execute(
|
|
709
|
+
\`INSERT INTO tbl_delivery_challans (challan_no, sale_id, customer_name, customer_phone, delivery_address, total_plants_qty, delivery_date, status, created_at)
|
|
710
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'dispatched', NOW())\`,
|
|
711
|
+
[challanNo, saleId, order.get('customer_name'), order.get('customer_phone'), order.get('customer_address'), order.get('total_items_count'), vhDate]
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
// 4. Update Online Order
|
|
715
|
+
await trx.execute(
|
|
716
|
+
\`UPDATE tbl_online_orders SET status = 'confirmed', sale_id = ?, updated_at = NOW() WHERE id = ?\`,
|
|
717
|
+
[saleId, orderId]
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
// 5. Post 4-Leg Double-Entry GL Journal
|
|
721
|
+
await this.ledgerService.postDeliveryVoucher(saleId, netAmount, trx);
|
|
722
|
+
|
|
723
|
+
return { success: true, order_id: orderId, sale_id: saleId, vh_no: vhNo, challan_no: challanNo };
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
public async paginate(page = 1, limit = 20, status?: string) {
|
|
728
|
+
let query = OnlineOrder.query().orderBy('id', 'DESC');
|
|
729
|
+
if (status) {
|
|
730
|
+
query = query.where('status', status);
|
|
731
|
+
}
|
|
732
|
+
return query.paginate(page, limit);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
`);
|
|
736
|
+
addFile('app/services/LedgerService.ts', `import { DB } from '../../database/connection.js';
|
|
737
|
+
|
|
738
|
+
export interface JournalEntry {
|
|
739
|
+
l_id: number;
|
|
740
|
+
for_l_id?: number;
|
|
741
|
+
dr_amt: number;
|
|
742
|
+
cr_amt: number;
|
|
743
|
+
notes: string;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export class LedgerService {
|
|
747
|
+
public async postDoubleEntryVoucher(
|
|
748
|
+
vhId: number | string,
|
|
749
|
+
vhNo: string,
|
|
750
|
+
vhDate: string,
|
|
751
|
+
entries: JournalEntry[],
|
|
752
|
+
trxAdapter?: any
|
|
753
|
+
): Promise<void> {
|
|
754
|
+
const totalDr = entries.reduce((sum, e) => sum + Number(e.dr_amt || 0), 0);
|
|
755
|
+
const totalCr = entries.reduce((sum, e) => sum + Number(e.cr_amt || 0), 0);
|
|
756
|
+
|
|
757
|
+
if (Math.abs(totalDr - totalCr) > 0.01) {
|
|
758
|
+
throw new Error(\`Double-Entry Violation: Debit (\${totalDr}) does not equal Credit (\${totalCr})\`);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const runner = trxAdapter || DB;
|
|
762
|
+
|
|
763
|
+
for (const e of entries) {
|
|
764
|
+
await runner.execute(
|
|
765
|
+
\`INSERT INTO tbl_ledger_transactions (vh_id, vh_no, vh_date, l_id, for_l_id, type, notes, dr_amt, cr_amt, amount, entrytime)
|
|
766
|
+
VALUES (?, ?, ?, ?, ?, 'journal', ?, ?, ?, ?, NOW())\`,
|
|
767
|
+
[vhId, vhNo, vhDate, e.l_id, e.for_l_id || 0, e.notes, e.dr_amt, e.cr_amt, e.dr_amt || e.cr_amt]
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
// Update current balance on tbl_ledger
|
|
771
|
+
const delta = Number(e.dr_amt) - Number(e.cr_amt);
|
|
772
|
+
await runner.execute(
|
|
773
|
+
\`UPDATE tbl_ledger SET c_balance = c_balance + ? WHERE l_id = ?\`,
|
|
774
|
+
[delta, e.l_id]
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* 4-Leg Double-Entry GL posting for Goods Dispatched/Delivered:
|
|
781
|
+
* 1. Dr. Courier Clearing / Accounts Receivable
|
|
782
|
+
* 2. Cr. Sales Revenue
|
|
783
|
+
* 3. Dr. Cost of Goods Sold (COGS)
|
|
784
|
+
* 4. Cr. Inventory Asset
|
|
785
|
+
*/
|
|
786
|
+
public async postDeliveryVoucher(saleId: number | string, totalSaleAmount: number, trxAdapter?: any): Promise<void> {
|
|
787
|
+
const vhDate = new Date().toISOString().slice(0, 10);
|
|
788
|
+
const vhNo = 'GL-DELIV-' + saleId;
|
|
789
|
+
|
|
790
|
+
// Approximate COGS at 60% of revenue for demonstration / standard margin
|
|
791
|
+
const cogsAmount = Math.round(totalSaleAmount * 0.6 * 100) / 100;
|
|
792
|
+
|
|
793
|
+
const entries: JournalEntry[] = [
|
|
794
|
+
// Leg 1: Dr. Courier Clearing (l_id: 4)
|
|
795
|
+
{ l_id: 4, for_l_id: 5, dr_amt: totalSaleAmount, cr_amt: 0, notes: \`Courier Clearing for Sale #\${saleId}\` },
|
|
796
|
+
// Leg 2: Cr. Sales Revenue (l_id: 5)
|
|
797
|
+
{ l_id: 5, for_l_id: 4, dr_amt: 0, cr_amt: totalSaleAmount, notes: \`Sales Revenue for Sale #\${saleId}\` },
|
|
798
|
+
// Leg 3: Dr. COGS (l_id: 6)
|
|
799
|
+
{ l_id: 6, for_l_id: 3, dr_amt: cogsAmount, cr_amt: 0, notes: \`COGS for Sale #\${saleId}\` },
|
|
800
|
+
// Leg 4: Cr. Inventory Asset (l_id: 3)
|
|
801
|
+
{ l_id: 3, for_l_id: 6, dr_amt: 0, cr_amt: cogsAmount, notes: \`Inventory Relief for Sale #\${saleId}\` },
|
|
802
|
+
];
|
|
803
|
+
|
|
804
|
+
await this.postDoubleEntryVoucher(saleId, vhNo, vhDate, entries, trxAdapter);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
public async getTrialBalance() {
|
|
808
|
+
const rows = await DB.query<any>(\`
|
|
809
|
+
SELECT
|
|
810
|
+
l.l_id, l.l_name, g.type as account_type,
|
|
811
|
+
COALESCE(SUM(t.dr_amt), 0) as total_debit,
|
|
812
|
+
COALESCE(SUM(t.cr_amt), 0) as total_credit,
|
|
813
|
+
(COALESCE(SUM(t.dr_amt), 0) - COALESCE(SUM(t.cr_amt), 0)) as net_balance
|
|
814
|
+
FROM tbl_ledger l
|
|
815
|
+
LEFT JOIN tbl_ledger_group g ON l.h_id = g.h_id
|
|
816
|
+
LEFT JOIN tbl_ledger_transactions t ON l.l_id = t.l_id
|
|
817
|
+
GROUP BY l.l_id, l.l_name, g.type
|
|
818
|
+
ORDER BY g.type, l.l_name
|
|
819
|
+
\`);
|
|
820
|
+
|
|
821
|
+
const grandDebit = rows.reduce((s, r) => s + Number(r.total_debit), 0);
|
|
822
|
+
const grandCredit = rows.reduce((s, r) => s + Number(r.total_credit), 0);
|
|
823
|
+
const isBalanced = Math.abs(grandDebit - grandCredit) < 0.01;
|
|
824
|
+
|
|
825
|
+
return { accounts: rows, grandDebit, grandCredit, isBalanced };
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
public async getBalanceSheet() {
|
|
829
|
+
const tb = await this.getTrialBalance();
|
|
830
|
+
const assets = tb.accounts.filter(a => a.account_type === 'Asset');
|
|
831
|
+
const liabilities = tb.accounts.filter(a => a.account_type === 'Liability');
|
|
832
|
+
const equity = tb.accounts.filter(a => a.account_type === 'Equity');
|
|
833
|
+
|
|
834
|
+
const totalAssets = assets.reduce((s, a) => s + Number(a.net_balance), 0);
|
|
835
|
+
const totalLiabilities = liabilities.reduce((s, a) => s + Number(a.net_balance), 0);
|
|
836
|
+
const totalEquity = equity.reduce((s, a) => s + Number(a.net_balance), 0);
|
|
837
|
+
|
|
838
|
+
return {
|
|
839
|
+
assets,
|
|
840
|
+
liabilities,
|
|
841
|
+
equity,
|
|
842
|
+
totalAssets,
|
|
843
|
+
totalLiabilitiesAndEquity: totalLiabilities + totalEquity,
|
|
844
|
+
isBalanced: Math.abs(totalAssets - (totalLiabilities + totalEquity)) < 0.01
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
public async auditIntegrityCheck() {
|
|
849
|
+
const result = await DB.query<any>(\`
|
|
850
|
+
SELECT
|
|
851
|
+
vh_no,
|
|
852
|
+
SUM(dr_amt) as total_dr,
|
|
853
|
+
SUM(cr_amt) as total_cr,
|
|
854
|
+
ABS(SUM(dr_amt) - SUM(cr_amt)) as diff
|
|
855
|
+
FROM tbl_ledger_transactions
|
|
856
|
+
GROUP BY vh_no
|
|
857
|
+
HAVING diff > 0.01
|
|
858
|
+
\`);
|
|
859
|
+
return {
|
|
860
|
+
isClean: result.length === 0,
|
|
861
|
+
violatingVouchers: result,
|
|
862
|
+
checkedAt: new Date().toISOString()
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
`);
|
|
867
|
+
addFile('app/services/CourierService.ts', `import { DB } from '../../database/connection.js';
|
|
868
|
+
import { OnlineOrder } from '../models/OnlineOrder.js';
|
|
869
|
+
|
|
870
|
+
export class CourierService {
|
|
871
|
+
public async handleWebhook(payload: {
|
|
872
|
+
consignment_id?: string;
|
|
873
|
+
order_id?: string | number;
|
|
874
|
+
status: string;
|
|
875
|
+
collected_amount?: number;
|
|
876
|
+
delivery_fee?: number;
|
|
877
|
+
}) {
|
|
878
|
+
return DB.transaction(async (trx) => {
|
|
879
|
+
const order = payload.order_id
|
|
880
|
+
? await OnlineOrder.find(payload.order_id, trx)
|
|
881
|
+
: await OnlineOrder.where('consignment_no', payload.consignment_id).first(trx);
|
|
882
|
+
|
|
883
|
+
if (!order) {
|
|
884
|
+
throw new Error(\`Order not found for courier webhook callback\`);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const courierStatus = payload.status.toLowerCase();
|
|
888
|
+
let orderStatus = order.get('status');
|
|
889
|
+
|
|
890
|
+
if (courierStatus.includes('delivered') || courierStatus === 'success') {
|
|
891
|
+
orderStatus = 'delivered';
|
|
892
|
+
} else if (courierStatus.includes('cancel') || courierStatus.includes('return')) {
|
|
893
|
+
orderStatus = 'returned';
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
await trx.execute(
|
|
897
|
+
\`UPDATE tbl_online_orders
|
|
898
|
+
SET courier_status = ?, status = ?, courier_delivery_fee = ?, updated_at = NOW()
|
|
899
|
+
WHERE id = ?\`,
|
|
900
|
+
[courierStatus, orderStatus, payload.delivery_fee || 0, order.get('id')]
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
return { success: true, order_id: order.get('id'), order_status: orderStatus };
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
`);
|
|
908
|
+
// 9. Controllers
|
|
909
|
+
addFile('app/controllers/DashboardController.ts', `import type { AeroContext } from 'aerojs';
|
|
910
|
+
import { DB } from '../../database/connection.js';
|
|
911
|
+
|
|
912
|
+
export class DashboardController {
|
|
913
|
+
public async index(ctx: AeroContext): Promise<void> {
|
|
914
|
+
const ordersCount = await DB.table('tbl_online_orders').count();
|
|
915
|
+
const productsCount = await DB.table('tbl_product').count();
|
|
916
|
+
const totalSalesRow = await DB.query<any>('SELECT COALESCE(SUM(net_amount), 0) as total FROM tbl_sale_head');
|
|
917
|
+
const recentOrders = await DB.table('tbl_online_orders').orderBy('id', 'DESC').limit(5);
|
|
918
|
+
|
|
919
|
+
const stats = {
|
|
920
|
+
totalOrders: Number(ordersCount),
|
|
921
|
+
totalProducts: Number(productsCount),
|
|
922
|
+
totalRevenue: Number(totalSalesRow[0]?.total || 0),
|
|
923
|
+
recentOrders,
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
if (ctx.req.get('X-Inertia') || ctx.req.path === '/dashboard' || ctx.req.path === '/') {
|
|
927
|
+
await ctx.inertia.render('Dashboard', stats);
|
|
928
|
+
} else {
|
|
929
|
+
ctx.status(200).json(stats);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
`);
|
|
934
|
+
addFile('app/controllers/ProductController.ts', `import type { AeroContext } from 'aerojs';
|
|
935
|
+
import { ProductService } from '../services/ProductService.js';
|
|
936
|
+
|
|
937
|
+
export class ProductController {
|
|
938
|
+
private productService = new ProductService();
|
|
939
|
+
|
|
940
|
+
public async index(ctx: AeroContext): Promise<void> {
|
|
941
|
+
const page = parseInt((ctx.req.query.page as string) || '1', 10);
|
|
942
|
+
const search = (ctx.req.query.search as string) || '';
|
|
943
|
+
const products = await this.productService.paginate(page, 20, search);
|
|
944
|
+
|
|
945
|
+
if (ctx.req.get('X-Inertia') || ctx.req.path === '/products') {
|
|
946
|
+
await ctx.inertia.render('Products/Index', { products });
|
|
947
|
+
} else {
|
|
948
|
+
ctx.status(200).json({ success: true, data: products });
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
public async show(ctx: AeroContext): Promise<void> {
|
|
953
|
+
const id = ctx.req.params.id;
|
|
954
|
+
const product = await this.productService.findById(id);
|
|
955
|
+
if (!product) {
|
|
956
|
+
ctx.status(404).json({ error: 'Product not found' });
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
ctx.status(200).json({ success: true, data: product });
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
`);
|
|
963
|
+
addFile('app/controllers/LandingPageController.ts', `import type { AeroContext } from 'aerojs';
|
|
964
|
+
import { ProductService } from '../services/ProductService.js';
|
|
965
|
+
|
|
966
|
+
export class LandingPageController {
|
|
967
|
+
private productService = new ProductService();
|
|
968
|
+
|
|
969
|
+
public async show(ctx: AeroContext): Promise<void> {
|
|
970
|
+
const slug = ctx.req.params.slug;
|
|
971
|
+
const result = await this.productService.findBySlug(slug);
|
|
972
|
+
if (!result.product) {
|
|
973
|
+
ctx.status(404).html('<h1>Product Not Found</h1>');
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (ctx.req.get('X-Inertia') || !ctx.req.path.startsWith('/api')) {
|
|
978
|
+
await ctx.inertia.render('LandingPage', {
|
|
979
|
+
product: result.product,
|
|
980
|
+
landing: result.landing,
|
|
981
|
+
});
|
|
982
|
+
} else {
|
|
983
|
+
ctx.status(200).json({ success: true, data: result });
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
`);
|
|
988
|
+
addFile('app/controllers/OrderController.ts', `import type { AeroContext } from 'aerojs';
|
|
989
|
+
import { OrderService } from '../services/OrderService.js';
|
|
990
|
+
|
|
991
|
+
export class OrderController {
|
|
992
|
+
private orderService = new OrderService();
|
|
993
|
+
|
|
994
|
+
public async index(ctx: AeroContext): Promise<void> {
|
|
995
|
+
const page = parseInt((ctx.req.query.page as string) || '1', 10);
|
|
996
|
+
const status = ctx.req.query.status as string;
|
|
997
|
+
const orders = await this.orderService.paginate(page, 20, status);
|
|
998
|
+
|
|
999
|
+
if (ctx.req.get('X-Inertia') || ctx.req.path === '/orders') {
|
|
1000
|
+
await ctx.inertia.render('Orders/Index', { orders });
|
|
1001
|
+
} else {
|
|
1002
|
+
ctx.status(200).json({ success: true, data: orders });
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
public async store(ctx: AeroContext): Promise<void> {
|
|
1007
|
+
const payload = ctx.body as any;
|
|
1008
|
+
try {
|
|
1009
|
+
const order = await this.orderService.createOrder(payload);
|
|
1010
|
+
ctx.status(201).json({ success: true, data: order });
|
|
1011
|
+
} catch (err: any) {
|
|
1012
|
+
ctx.status(422).json({ error: err.message });
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
public async confirm(ctx: AeroContext): Promise<void> {
|
|
1017
|
+
const id = ctx.req.params.id;
|
|
1018
|
+
try {
|
|
1019
|
+
const result = await this.orderService.confirmOrder(id);
|
|
1020
|
+
ctx.status(200).json(result);
|
|
1021
|
+
} catch (err: any) {
|
|
1022
|
+
ctx.status(400).json({ error: err.message });
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
`);
|
|
1027
|
+
addFile('app/controllers/LedgerController.ts', `import type { AeroContext } from 'aerojs';
|
|
1028
|
+
import { LedgerService } from '../services/LedgerService.js';
|
|
1029
|
+
|
|
1030
|
+
export class LedgerController {
|
|
1031
|
+
private ledgerService = new LedgerService();
|
|
1032
|
+
|
|
1033
|
+
public async trialBalance(ctx: AeroContext): Promise<void> {
|
|
1034
|
+
const tb = await this.ledgerService.getTrialBalance();
|
|
1035
|
+
if (ctx.req.get('X-Inertia') || ctx.req.path === '/ledger') {
|
|
1036
|
+
await ctx.inertia.render('Ledger/Index', { trialBalance: tb });
|
|
1037
|
+
} else {
|
|
1038
|
+
ctx.status(200).json({ success: true, data: tb });
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
public async balanceSheet(ctx: AeroContext): Promise<void> {
|
|
1043
|
+
const bs = await this.ledgerService.getBalanceSheet();
|
|
1044
|
+
ctx.status(200).json({ success: true, data: bs });
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
public async audit(ctx: AeroContext): Promise<void> {
|
|
1048
|
+
const check = await this.ledgerService.auditIntegrityCheck();
|
|
1049
|
+
ctx.status(200).json({ success: true, data: check });
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
`);
|
|
1053
|
+
addFile('app/controllers/CourierWebhookController.ts', `import type { AeroContext } from 'aerojs';
|
|
1054
|
+
import { CourierService } from '../services/CourierService.js';
|
|
1055
|
+
|
|
1056
|
+
export class CourierWebhookController {
|
|
1057
|
+
private courierService = new CourierService();
|
|
1058
|
+
|
|
1059
|
+
public async handle(ctx: AeroContext): Promise<void> {
|
|
1060
|
+
const body = ctx.body as any;
|
|
1061
|
+
try {
|
|
1062
|
+
const result = await this.courierService.handleWebhook(body);
|
|
1063
|
+
ctx.status(200).json(result);
|
|
1064
|
+
} catch (err: any) {
|
|
1065
|
+
ctx.status(400).json({ error: err.message });
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
`);
|
|
1070
|
+
addFile('app/controllers/AuthController.ts', `import type { AeroContext } from 'aerojs';
|
|
1071
|
+
import { DB } from '../../database/connection.js';
|
|
1072
|
+
|
|
1073
|
+
export class AuthController {
|
|
1074
|
+
public async login(ctx: AeroContext): Promise<void> {
|
|
1075
|
+
const { email, password } = (ctx.body as any) || {};
|
|
1076
|
+
// Demo admin authentication
|
|
1077
|
+
if (email === 'admin@nursery.dev' && password === 'admin123') {
|
|
1078
|
+
ctx.status(200).json({
|
|
1079
|
+
success: true,
|
|
1080
|
+
token: 'demo-jwt-token-for-admin-erp',
|
|
1081
|
+
user: { id: 1, name: 'Managing Director', role: 'admin', email },
|
|
1082
|
+
});
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
ctx.status(401).json({ error: 'Invalid email or password' });
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
public async me(ctx: AeroContext): Promise<void> {
|
|
1089
|
+
ctx.status(200).json({
|
|
1090
|
+
success: true,
|
|
1091
|
+
user: { id: 1, name: 'Managing Director', role: 'admin', email: 'admin@nursery.dev' },
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
`);
|
|
1096
|
+
// 10. Routes
|
|
1097
|
+
addFile('routes/api.ts', `import type { Router } from 'aerojs';
|
|
1098
|
+
import { ProductController } from '../app/controllers/ProductController.js';
|
|
1099
|
+
import { OrderController } from '../app/controllers/OrderController.js';
|
|
1100
|
+
import { LedgerController } from '../app/controllers/LedgerController.js';
|
|
1101
|
+
import { CourierWebhookController } from '../app/controllers/CourierWebhookController.js';
|
|
1102
|
+
|
|
1103
|
+
export function registerApiRoutes(router: Router): void {
|
|
1104
|
+
router.group('/api/v1', (api) => {
|
|
1105
|
+
// Products
|
|
1106
|
+
api.get('/products', [ProductController, 'index']);
|
|
1107
|
+
api.get('/products/:id', [ProductController, 'show']);
|
|
1108
|
+
|
|
1109
|
+
// Orders
|
|
1110
|
+
api.get('/orders', [OrderController, 'index']);
|
|
1111
|
+
api.post('/orders', [OrderController, 'store']);
|
|
1112
|
+
api.post('/orders/:id/confirm', [OrderController, 'confirm']);
|
|
1113
|
+
|
|
1114
|
+
// Accounting & GL
|
|
1115
|
+
api.get('/ledger/trial-balance', [LedgerController, 'trialBalance']);
|
|
1116
|
+
api.get('/ledger/balance-sheet', [LedgerController, 'balanceSheet']);
|
|
1117
|
+
api.get('/ledger/audit', [LedgerController, 'audit']);
|
|
1118
|
+
|
|
1119
|
+
// 3PL Couriers
|
|
1120
|
+
api.post('/webhooks/courier', [CourierWebhookController, 'handle']);
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
`);
|
|
1124
|
+
addFile('routes/web.ts', `import type { Router } from 'aerojs';
|
|
1125
|
+
import { DashboardController } from '../app/controllers/DashboardController.js';
|
|
1126
|
+
import { ProductController } from '../app/controllers/ProductController.js';
|
|
1127
|
+
import { OrderController } from '../app/controllers/OrderController.js';
|
|
1128
|
+
import { LedgerController } from '../app/controllers/LedgerController.js';
|
|
1129
|
+
import { LandingPageController } from '../app/controllers/LandingPageController.js';
|
|
1130
|
+
|
|
1131
|
+
export function registerWebRoutes(router: Router): void {
|
|
1132
|
+
router.get('/', [DashboardController, 'index']);
|
|
1133
|
+
router.get('/dashboard', [DashboardController, 'index']);
|
|
1134
|
+
router.get('/products', [ProductController, 'index']);
|
|
1135
|
+
router.get('/orders', [OrderController, 'index']);
|
|
1136
|
+
router.get('/ledger', [LedgerController, 'trialBalance']);
|
|
1137
|
+
router.get('/p/:slug', [LandingPageController, 'show']);
|
|
1138
|
+
}
|
|
1139
|
+
`);
|
|
1140
|
+
addFile('routes/auth.ts', `import type { Router } from 'aerojs';
|
|
1141
|
+
import { AuthController } from '../app/controllers/AuthController.js';
|
|
1142
|
+
|
|
1143
|
+
export function registerAuthRoutes(router: Router): void {
|
|
1144
|
+
router.group('/auth', (auth) => {
|
|
1145
|
+
auth.post('/login', [AuthController, 'login']);
|
|
1146
|
+
auth.get('/me', [AuthController, 'me']);
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
`);
|
|
1150
|
+
// 11. Frontend - Vue 3 & Inertia
|
|
1151
|
+
addFile('resources/js/app.ts', `import { createApp, h, type DefineComponent } from 'vue';
|
|
1152
|
+
import { createInertiaApp } from '@inertiajs/vue3';
|
|
1153
|
+
import '../css/app.css';
|
|
1154
|
+
|
|
1155
|
+
createInertiaApp({
|
|
1156
|
+
resolve: (name: string) => {
|
|
1157
|
+
const pages = (import.meta as any).glob('./Pages/**/*.vue', { eager: true });
|
|
1158
|
+
const page = pages[\`./Pages/\${name}.vue\`];
|
|
1159
|
+
if (!page) {
|
|
1160
|
+
throw new Error(\`Page component "\${name}" not found.\`);
|
|
1161
|
+
}
|
|
1162
|
+
return page.default as DefineComponent;
|
|
1163
|
+
},
|
|
1164
|
+
setup({ el, App, props, plugin }) {
|
|
1165
|
+
createApp({ render: () => h(App, props) })
|
|
1166
|
+
.use(plugin)
|
|
1167
|
+
.mount(el);
|
|
1168
|
+
},
|
|
1169
|
+
});
|
|
1170
|
+
`);
|
|
1171
|
+
addFile('resources/css/app.css', `:root {
|
|
1172
|
+
--bg-main: #0b1329;
|
|
1173
|
+
--bg-card: rgba(18, 28, 56, 0.85);
|
|
1174
|
+
--border-color: rgba(255, 255, 255, 0.1);
|
|
1175
|
+
--accent-green: #10b981;
|
|
1176
|
+
--accent-blue: #3b82f6;
|
|
1177
|
+
--text-main: #f3f4f6;
|
|
1178
|
+
--text-muted: #9ca3af;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
body {
|
|
1182
|
+
margin: 0;
|
|
1183
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
1184
|
+
background-color: var(--bg-main);
|
|
1185
|
+
color: var(--text-main);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.erp-card {
|
|
1189
|
+
background: var(--bg-card);
|
|
1190
|
+
border: 1px solid var(--border-color);
|
|
1191
|
+
border-radius: 12px;
|
|
1192
|
+
backdrop-filter: blur(16px);
|
|
1193
|
+
padding: 20px;
|
|
1194
|
+
}
|
|
1195
|
+
`);
|
|
1196
|
+
addFile('resources/js/Pages/Dashboard.vue', `<template>
|
|
1197
|
+
<div class="min-h-screen bg-slate-950 text-slate-100 p-6">
|
|
1198
|
+
<div class="max-w-7xl mx-auto space-y-6">
|
|
1199
|
+
<!-- Header -->
|
|
1200
|
+
<div class="flex items-center justify-between pb-4 border-b border-slate-800">
|
|
1201
|
+
<div>
|
|
1202
|
+
<h1 class="text-3xl font-extrabold bg-gradient-to-r from-emerald-400 to-teal-400 bg-clip-text text-transparent">
|
|
1203
|
+
Nursery & Omnichannel E-Commerce ERP
|
|
1204
|
+
</h1>
|
|
1205
|
+
<p class="text-sm text-slate-400 mt-1">Real-Time Enterprise Executive Dashboard</p>
|
|
1206
|
+
</div>
|
|
1207
|
+
<div class="flex gap-3">
|
|
1208
|
+
<a href="/products" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 rounded-lg text-sm font-medium transition">Catalog</a>
|
|
1209
|
+
<a href="/orders" class="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 rounded-lg text-sm font-medium text-white transition">Orders</a>
|
|
1210
|
+
<a href="/ledger" class="px-4 py-2 bg-blue-600 hover:bg-blue-500 rounded-lg text-sm font-medium text-white transition">Ledger</a>
|
|
1211
|
+
</div>
|
|
1212
|
+
</div>
|
|
1213
|
+
|
|
1214
|
+
<!-- Stats Grid -->
|
|
1215
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
1216
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 shadow-xl">
|
|
1217
|
+
<span class="text-xs uppercase tracking-wider text-slate-400">Total Revenue</span>
|
|
1218
|
+
<div class="text-3xl font-bold text-emerald-400 mt-2">৳ {{ totalRevenue.toLocaleString() }}</div>
|
|
1219
|
+
</div>
|
|
1220
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 shadow-xl">
|
|
1221
|
+
<span class="text-xs uppercase tracking-wider text-slate-400">Total Orders</span>
|
|
1222
|
+
<div class="text-3xl font-bold text-blue-400 mt-2">{{ totalOrders }}</div>
|
|
1223
|
+
</div>
|
|
1224
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 shadow-xl">
|
|
1225
|
+
<span class="text-xs uppercase tracking-wider text-slate-400">Active Plants & SKUs</span>
|
|
1226
|
+
<div class="text-3xl font-bold text-teal-400 mt-2">{{ totalProducts }}</div>
|
|
1227
|
+
</div>
|
|
1228
|
+
</div>
|
|
1229
|
+
|
|
1230
|
+
<!-- Recent Orders Table -->
|
|
1231
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 shadow-xl space-y-4">
|
|
1232
|
+
<h2 class="text-lg font-bold text-slate-200">Recent Omnichannel Orders</h2>
|
|
1233
|
+
<div class="overflow-x-auto">
|
|
1234
|
+
<table class="w-full text-left text-sm">
|
|
1235
|
+
<thead class="text-xs text-slate-400 uppercase bg-slate-800/50">
|
|
1236
|
+
<tr>
|
|
1237
|
+
<th class="p-3">Order No</th>
|
|
1238
|
+
<th class="p-3">Customer</th>
|
|
1239
|
+
<th class="p-3">District</th>
|
|
1240
|
+
<th class="p-3">Amount</th>
|
|
1241
|
+
<th class="p-3">Status</th>
|
|
1242
|
+
</tr>
|
|
1243
|
+
</thead>
|
|
1244
|
+
<tbody>
|
|
1245
|
+
<tr v-for="order in recentOrders" :key="order.id" class="border-b border-slate-800/50 hover:bg-slate-800/30">
|
|
1246
|
+
<td class="p-3 font-mono text-emerald-400">{{ order.order_no }}</td>
|
|
1247
|
+
<td class="p-3">{{ order.customer_name }}</td>
|
|
1248
|
+
<td class="p-3">{{ order.delivery_district }}</td>
|
|
1249
|
+
<td class="p-3 font-semibold">৳ {{ order.total_amount }}</td>
|
|
1250
|
+
<td class="p-3">
|
|
1251
|
+
<span class="px-2.5 py-1 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
|
|
1252
|
+
{{ order.status }}
|
|
1253
|
+
</span>
|
|
1254
|
+
</td>
|
|
1255
|
+
</tr>
|
|
1256
|
+
<tr v-if="!recentOrders || recentOrders.length === 0">
|
|
1257
|
+
<td colspan="5" class="p-4 text-center text-slate-500">No orders placed yet.</td>
|
|
1258
|
+
</tr>
|
|
1259
|
+
</tbody>
|
|
1260
|
+
</table>
|
|
1261
|
+
</div>
|
|
1262
|
+
</div>
|
|
1263
|
+
</div>
|
|
1264
|
+
</div>
|
|
1265
|
+
</template>
|
|
1266
|
+
|
|
1267
|
+
<script setup>
|
|
1268
|
+
defineProps({
|
|
1269
|
+
totalRevenue: { type: Number, default: 0 },
|
|
1270
|
+
totalOrders: { type: Number, default: 0 },
|
|
1271
|
+
totalProducts: { type: Number, default: 0 },
|
|
1272
|
+
recentOrders: { type: Array, default: () => [] }
|
|
1273
|
+
});
|
|
1274
|
+
</script>
|
|
1275
|
+
`);
|
|
1276
|
+
addFile('resources/js/Pages/Products/Index.vue', `<template>
|
|
1277
|
+
<div class="min-h-screen bg-slate-950 text-slate-100 p-6">
|
|
1278
|
+
<div class="max-w-7xl mx-auto space-y-6">
|
|
1279
|
+
<div class="flex items-center justify-between pb-4 border-b border-slate-800">
|
|
1280
|
+
<div>
|
|
1281
|
+
<h1 class="text-2xl font-bold">Plant & Product Inventory</h1>
|
|
1282
|
+
<p class="text-xs text-slate-400 mt-1">Multi-UOM SPU/SKU Stock Management</p>
|
|
1283
|
+
</div>
|
|
1284
|
+
<a href="/dashboard" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 rounded-lg text-sm">Back to Dashboard</a>
|
|
1285
|
+
</div>
|
|
1286
|
+
|
|
1287
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 overflow-x-auto">
|
|
1288
|
+
<table class="w-full text-left text-sm">
|
|
1289
|
+
<thead class="text-xs text-slate-400 uppercase bg-slate-800/50">
|
|
1290
|
+
<tr>
|
|
1291
|
+
<th class="p-3">ID</th>
|
|
1292
|
+
<th class="p-3">Product Name</th>
|
|
1293
|
+
<th class="p-3">Scientific Name</th>
|
|
1294
|
+
<th class="p-3">Sale Rate</th>
|
|
1295
|
+
<th class="p-3">Available Stock</th>
|
|
1296
|
+
</tr>
|
|
1297
|
+
</thead>
|
|
1298
|
+
<tbody>
|
|
1299
|
+
<tr v-for="p in products.data" :key="p.product_id" class="border-b border-slate-800/50">
|
|
1300
|
+
<td class="p-3 font-mono text-slate-400">#{{ p.product_id }}</td>
|
|
1301
|
+
<td class="p-3 font-semibold text-emerald-400">{{ p.product_name }}</td>
|
|
1302
|
+
<td class="p-3 italic text-slate-400">{{ p.scientific_name || '-' }}</td>
|
|
1303
|
+
<td class="p-3">৳ {{ p.sale_rate }}</td>
|
|
1304
|
+
<td class="p-3">
|
|
1305
|
+
<span :class="p.c_stock > 0 ? 'text-emerald-400 font-bold' : 'text-rose-400 font-bold'">
|
|
1306
|
+
{{ p.c_stock }} {{ p.base_uom || 'pcs' }}
|
|
1307
|
+
</span>
|
|
1308
|
+
</td>
|
|
1309
|
+
</tr>
|
|
1310
|
+
</tbody>
|
|
1311
|
+
</table>
|
|
1312
|
+
</div>
|
|
1313
|
+
</div>
|
|
1314
|
+
</div>
|
|
1315
|
+
</template>
|
|
1316
|
+
|
|
1317
|
+
<script setup>
|
|
1318
|
+
defineProps({
|
|
1319
|
+
products: { type: Object, default: () => ({ data: [] }) }
|
|
1320
|
+
});
|
|
1321
|
+
</script>
|
|
1322
|
+
`);
|
|
1323
|
+
addFile('resources/js/Pages/Orders/Index.vue', `<template>
|
|
1324
|
+
<div class="min-h-screen bg-slate-950 text-slate-100 p-6">
|
|
1325
|
+
<div class="max-w-7xl mx-auto space-y-6">
|
|
1326
|
+
<div class="flex items-center justify-between pb-4 border-b border-slate-800">
|
|
1327
|
+
<div>
|
|
1328
|
+
<h1 class="text-2xl font-bold">Omnichannel Orders Pipeline</h1>
|
|
1329
|
+
<p class="text-xs text-slate-400 mt-1">O2C Lifecycle & 3PL Courier Dispatch</p>
|
|
1330
|
+
</div>
|
|
1331
|
+
<a href="/dashboard" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 rounded-lg text-sm">Back to Dashboard</a>
|
|
1332
|
+
</div>
|
|
1333
|
+
|
|
1334
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 overflow-x-auto">
|
|
1335
|
+
<table class="w-full text-left text-sm">
|
|
1336
|
+
<thead class="text-xs text-slate-400 uppercase bg-slate-800/50">
|
|
1337
|
+
<tr>
|
|
1338
|
+
<th class="p-3">Order No</th>
|
|
1339
|
+
<th class="p-3">Customer</th>
|
|
1340
|
+
<th class="p-3">Phone</th>
|
|
1341
|
+
<th class="p-3">Total Amount</th>
|
|
1342
|
+
<th class="p-3">Status</th>
|
|
1343
|
+
<th class="p-3">Action</th>
|
|
1344
|
+
</tr>
|
|
1345
|
+
</thead>
|
|
1346
|
+
<tbody>
|
|
1347
|
+
<tr v-for="order in orders.data" :key="order.id" class="border-b border-slate-800/50">
|
|
1348
|
+
<td class="p-3 font-mono text-emerald-400">{{ order.order_no }}</td>
|
|
1349
|
+
<td class="p-3 font-semibold">{{ order.customer_name }}</td>
|
|
1350
|
+
<td class="p-3 text-slate-400">{{ order.customer_phone }}</td>
|
|
1351
|
+
<td class="p-3">৳ {{ order.total_amount }}</td>
|
|
1352
|
+
<td class="p-3">
|
|
1353
|
+
<span class="px-2.5 py-1 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
|
|
1354
|
+
{{ order.status }}
|
|
1355
|
+
</span>
|
|
1356
|
+
</td>
|
|
1357
|
+
<td class="p-3">
|
|
1358
|
+
<button
|
|
1359
|
+
v-if="order.status === 'pending'"
|
|
1360
|
+
@click="confirmOrder(order.id)"
|
|
1361
|
+
class="px-3 py-1 bg-emerald-600 hover:bg-emerald-500 rounded text-xs text-white cursor-pointer"
|
|
1362
|
+
>
|
|
1363
|
+
Confirm & Dispatch
|
|
1364
|
+
</button>
|
|
1365
|
+
</td>
|
|
1366
|
+
</tr>
|
|
1367
|
+
</tbody>
|
|
1368
|
+
</table>
|
|
1369
|
+
</div>
|
|
1370
|
+
</div>
|
|
1371
|
+
</div>
|
|
1372
|
+
</template>
|
|
1373
|
+
|
|
1374
|
+
<script setup>
|
|
1375
|
+
defineProps({
|
|
1376
|
+
orders: { type: Object, default: () => ({ data: [] }) }
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
const confirmOrder = async (id) => {
|
|
1380
|
+
if (!confirm('Confirm and dispatch order #' + id + '?')) return;
|
|
1381
|
+
const res = await fetch('/api/v1/orders/' + id + '/confirm', { method: 'POST' });
|
|
1382
|
+
if (res.ok) window.location.reload();
|
|
1383
|
+
};
|
|
1384
|
+
</script>
|
|
1385
|
+
`);
|
|
1386
|
+
addFile('resources/js/Pages/Ledger/Index.vue', `<template>
|
|
1387
|
+
<div class="min-h-screen bg-slate-950 text-slate-100 p-6">
|
|
1388
|
+
<div class="max-w-7xl mx-auto space-y-6">
|
|
1389
|
+
<div class="flex items-center justify-between pb-4 border-b border-slate-800">
|
|
1390
|
+
<div>
|
|
1391
|
+
<h1 class="text-2xl font-bold">General Ledger & Trial Balance</h1>
|
|
1392
|
+
<p class="text-xs text-slate-400 mt-1">Double-Entry Financial Accounting Control</p>
|
|
1393
|
+
</div>
|
|
1394
|
+
<a href="/dashboard" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 rounded-lg text-sm">Back to Dashboard</a>
|
|
1395
|
+
</div>
|
|
1396
|
+
|
|
1397
|
+
<div class="p-6 rounded-2xl bg-slate-900/80 border border-slate-800 space-y-4">
|
|
1398
|
+
<div class="flex items-center justify-between">
|
|
1399
|
+
<h2 class="text-lg font-bold">Trial Balance Summary</h2>
|
|
1400
|
+
<span
|
|
1401
|
+
:class="trialBalance.isBalanced ? 'px-3 py-1 bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 rounded-full text-xs font-bold' : 'px-3 py-1 bg-rose-500/10 text-rose-400 border border-rose-500/20 rounded-full text-xs font-bold'"
|
|
1402
|
+
>
|
|
1403
|
+
{{ trialBalance.isBalanced ? '✓ Double-Entry In Balance' : '⚠ Out of Balance' }}
|
|
1404
|
+
</span>
|
|
1405
|
+
</div>
|
|
1406
|
+
|
|
1407
|
+
<div class="overflow-x-auto">
|
|
1408
|
+
<table class="w-full text-left text-sm">
|
|
1409
|
+
<thead class="text-xs text-slate-400 uppercase bg-slate-800/50">
|
|
1410
|
+
<tr>
|
|
1411
|
+
<th class="p-3">Account Name</th>
|
|
1412
|
+
<th class="p-3">Type</th>
|
|
1413
|
+
<th class="p-3 text-right">Debit (৳)</th>
|
|
1414
|
+
<th class="p-3 text-right">Credit (৳)</th>
|
|
1415
|
+
<th class="p-3 text-right">Net Balance (৳)</th>
|
|
1416
|
+
</tr>
|
|
1417
|
+
</thead>
|
|
1418
|
+
<tbody>
|
|
1419
|
+
<tr v-for="acc in trialBalance.accounts" :key="acc.l_id" class="border-b border-slate-800/50">
|
|
1420
|
+
<td class="p-3 font-semibold text-slate-200">{{ acc.l_name }}</td>
|
|
1421
|
+
<td class="p-3 text-slate-400">{{ acc.account_type }}</td>
|
|
1422
|
+
<td class="p-3 text-right font-mono">{{ Number(acc.total_debit).toFixed(2) }}</td>
|
|
1423
|
+
<td class="p-3 text-right font-mono">{{ Number(acc.total_credit).toFixed(2) }}</td>
|
|
1424
|
+
<td class="p-3 text-right font-mono font-bold" :class="acc.net_balance >= 0 ? 'text-emerald-400' : 'text-rose-400'">
|
|
1425
|
+
{{ Number(acc.net_balance).toFixed(2) }}
|
|
1426
|
+
</td>
|
|
1427
|
+
</tr>
|
|
1428
|
+
</tbody>
|
|
1429
|
+
<tfoot class="font-bold bg-slate-800/30">
|
|
1430
|
+
<tr>
|
|
1431
|
+
<td class="p-3" colspan="2">Grand Total</td>
|
|
1432
|
+
<td class="p-3 text-right font-mono text-emerald-400">{{ Number(trialBalance.grandDebit || 0).toFixed(2) }}</td>
|
|
1433
|
+
<td class="p-3 text-right font-mono text-emerald-400">{{ Number(trialBalance.grandCredit || 0).toFixed(2) }}</td>
|
|
1434
|
+
<td class="p-3 text-right font-mono text-emerald-400">0.00</td>
|
|
1435
|
+
</tr>
|
|
1436
|
+
</tfoot>
|
|
1437
|
+
</table>
|
|
1438
|
+
</div>
|
|
1439
|
+
</div>
|
|
1440
|
+
</div>
|
|
1441
|
+
</div>
|
|
1442
|
+
</template>
|
|
1443
|
+
|
|
1444
|
+
<script setup>
|
|
1445
|
+
defineProps({
|
|
1446
|
+
trialBalance: { type: Object, default: () => ({ accounts: [], isBalanced: true, grandDebit: 0, grandCredit: 0 }) }
|
|
1447
|
+
});
|
|
1448
|
+
</script>
|
|
1449
|
+
`);
|
|
1450
|
+
addFile('resources/js/Pages/LandingPage.vue', `<template>
|
|
1451
|
+
<div class="min-h-screen bg-slate-950 text-slate-100 flex flex-col items-center p-4 sm:p-6 font-sans">
|
|
1452
|
+
<div class="max-w-xl w-full bg-slate-900 border border-slate-800 rounded-3xl p-6 shadow-2xl space-y-6">
|
|
1453
|
+
<!-- Badge & Title -->
|
|
1454
|
+
<div class="text-center space-y-2">
|
|
1455
|
+
<span class="px-3 py-1 rounded-full text-xs font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
|
|
1456
|
+
🌿 100% Authentic Nursery Plant
|
|
1457
|
+
</span>
|
|
1458
|
+
<h1 class="text-3xl font-extrabold text-slate-100">{{ product.product_name }}</h1>
|
|
1459
|
+
<p class="text-sm text-slate-400 italic">{{ product.scientific_name }}</p>
|
|
1460
|
+
</div>
|
|
1461
|
+
|
|
1462
|
+
<!-- Price & Stock -->
|
|
1463
|
+
<div class="flex items-center justify-between p-4 rounded-2xl bg-slate-950/60 border border-slate-800">
|
|
1464
|
+
<div>
|
|
1465
|
+
<span class="text-xs text-slate-400">Special Offer Price</span>
|
|
1466
|
+
<div class="text-2xl font-bold text-emerald-400">৳ {{ product.sale_rate }}</div>
|
|
1467
|
+
</div>
|
|
1468
|
+
<div class="text-right">
|
|
1469
|
+
<span class="text-xs text-slate-400">Availability</span>
|
|
1470
|
+
<div class="text-sm font-semibold text-teal-400">In Stock ({{ product.c_stock }})</div>
|
|
1471
|
+
</div>
|
|
1472
|
+
</div>
|
|
1473
|
+
|
|
1474
|
+
<!-- Fast Checkout Form -->
|
|
1475
|
+
<form @submit.prevent="submitOrder" class="space-y-4">
|
|
1476
|
+
<h3 class="font-bold text-slate-200">Delivery Information (Cash on Delivery)</h3>
|
|
1477
|
+
<div>
|
|
1478
|
+
<label class="text-xs text-slate-400">Your Full Name</label>
|
|
1479
|
+
<input v-model="form.customer_name" required class="w-full mt-1 p-3 bg-slate-950 border border-slate-800 rounded-xl text-sm" placeholder="e.g. Tanvir Ahmed" />
|
|
1480
|
+
</div>
|
|
1481
|
+
<div>
|
|
1482
|
+
<label class="text-xs text-slate-400">Mobile Phone</label>
|
|
1483
|
+
<input v-model="form.customer_phone" required class="w-full mt-1 p-3 bg-slate-950 border border-slate-800 rounded-xl text-sm" placeholder="e.g. 01700000000" />
|
|
1484
|
+
</div>
|
|
1485
|
+
<div>
|
|
1486
|
+
<label class="text-xs text-slate-400">Delivery District</label>
|
|
1487
|
+
<input v-model="form.delivery_district" required class="w-full mt-1 p-3 bg-slate-950 border border-slate-800 rounded-xl text-sm" placeholder="e.g. Dhaka" />
|
|
1488
|
+
</div>
|
|
1489
|
+
<div>
|
|
1490
|
+
<label class="text-xs text-slate-400">Full Address</label>
|
|
1491
|
+
<textarea v-model="form.customer_address" required rows="2" class="w-full mt-1 p-3 bg-slate-950 border border-slate-800 rounded-xl text-sm" placeholder="House, Road, Area..."></textarea>
|
|
1492
|
+
</div>
|
|
1493
|
+
|
|
1494
|
+
<button type="submit" :disabled="loading" class="w-full py-4 bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 text-white font-bold rounded-2xl shadow-lg shadow-emerald-600/30 transition cursor-pointer">
|
|
1495
|
+
{{ loading ? 'Placing Order...' : 'Confirm Cash on Delivery Order' }}
|
|
1496
|
+
</button>
|
|
1497
|
+
</form>
|
|
1498
|
+
</div>
|
|
1499
|
+
</div>
|
|
1500
|
+
</template>
|
|
1501
|
+
|
|
1502
|
+
<script setup>
|
|
1503
|
+
import { reactive, ref } from 'vue';
|
|
1504
|
+
|
|
1505
|
+
const props = defineProps({
|
|
1506
|
+
product: { type: Object, required: true },
|
|
1507
|
+
landing: { type: Object, default: null }
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
const loading = ref(false);
|
|
1511
|
+
const form = reactive({
|
|
1512
|
+
customer_name: '',
|
|
1513
|
+
customer_phone: '',
|
|
1514
|
+
customer_address: '',
|
|
1515
|
+
delivery_district: 'Dhaka',
|
|
1516
|
+
items: [{ product_id: props.product.product_id, qty: 1 }]
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
const submitOrder = async () => {
|
|
1520
|
+
loading.value = true;
|
|
1521
|
+
try {
|
|
1522
|
+
const res = await fetch('/api/v1/orders', {
|
|
1523
|
+
method: 'POST',
|
|
1524
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1525
|
+
body: JSON.stringify(form)
|
|
1526
|
+
});
|
|
1527
|
+
const data = await res.json();
|
|
1528
|
+
if (res.ok) {
|
|
1529
|
+
alert('Order Placed Successfully! Order No: ' + data.data.order_no);
|
|
1530
|
+
window.location.href = '/dashboard';
|
|
1531
|
+
} else {
|
|
1532
|
+
alert('Error: ' + data.error);
|
|
1533
|
+
}
|
|
1534
|
+
} finally {
|
|
1535
|
+
loading.value = false;
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
</script>
|
|
1539
|
+
`);
|
|
1540
|
+
// 12. NodeFlow-inspired CLI Toolkit (cli.ts)
|
|
1541
|
+
addFile('cli.ts', `import readline from 'readline';
|
|
1542
|
+
import { initDatabase, DB, closeDatabase } from './database/connection.js';
|
|
1543
|
+
import { seed } from './database/seed.js';
|
|
1544
|
+
import { LedgerService } from './app/services/LedgerService.js';
|
|
1545
|
+
|
|
1546
|
+
const rl = readline.createInterface({
|
|
1547
|
+
input: process.stdin,
|
|
1548
|
+
output: process.stdout,
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
const colors = {
|
|
1552
|
+
reset: "\\x1b[0m",
|
|
1553
|
+
bold: "\\x1b[1m",
|
|
1554
|
+
green: "\\x1b[32m",
|
|
1555
|
+
yellow: "\\x1b[33m",
|
|
1556
|
+
blue: "\\x1b[34m",
|
|
1557
|
+
cyan: "\\x1b[36m",
|
|
1558
|
+
red: "\\x1b[31m",
|
|
1559
|
+
};
|
|
1560
|
+
|
|
1561
|
+
function printMenu() {
|
|
1562
|
+
console.log(colors.cyan + "\\n╔══════════════════════════════════════════════════════════╗" + colors.reset);
|
|
1563
|
+
console.log(colors.cyan + "║ " + colors.bold + "NURSERY & E-COMMERCE ERP CLI (AeroJS Powered)" + colors.cyan + " ║" + colors.reset);
|
|
1564
|
+
console.log(colors.cyan + "╚══════════════════════════════════════════════════════════╝" + colors.reset);
|
|
1565
|
+
console.log("1. " + colors.green + "View Database Tables & Status" + colors.reset);
|
|
1566
|
+
console.log("2. " + colors.green + "Run Database Seed (Default Chart of Accounts)" + colors.reset);
|
|
1567
|
+
console.log("3. " + colors.yellow + "Financial Integrity Audit (Validate Double-Entry Dr == Cr)" + colors.reset);
|
|
1568
|
+
console.log("4. " + colors.blue + "View Real-Time Trial Balance" + colors.reset);
|
|
1569
|
+
console.log("5. " + colors.cyan + "View Balance Sheet" + colors.reset);
|
|
1570
|
+
console.log("0. " + colors.red + "Exit" + colors.reset);
|
|
1571
|
+
rl.question(colors.yellow + "\\nSelect option: " + colors.reset, handleChoice);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
async function handleChoice(choice: string) {
|
|
1575
|
+
try {
|
|
1576
|
+
await initDatabase();
|
|
1577
|
+
switch (choice.trim()) {
|
|
1578
|
+
case '1': {
|
|
1579
|
+
const tables = await DB.query<any>('SHOW TABLES');
|
|
1580
|
+
console.log(colors.green + \`\\nTotal Active Database Tables: \${tables.length}\\n\` + colors.reset);
|
|
1581
|
+
for (const t of tables.slice(0, 10)) {
|
|
1582
|
+
console.log(' - ' + Object.values(t)[0]);
|
|
1583
|
+
}
|
|
1584
|
+
if (tables.length > 10) console.log(\` ... and \${tables.length - 10} more tables.\`);
|
|
1585
|
+
break;
|
|
1586
|
+
}
|
|
1587
|
+
case '2': {
|
|
1588
|
+
await seed();
|
|
1589
|
+
break;
|
|
1590
|
+
}
|
|
1591
|
+
case '3': {
|
|
1592
|
+
const service = new LedgerService();
|
|
1593
|
+
const audit = await service.auditIntegrityCheck();
|
|
1594
|
+
if (audit.isClean) {
|
|
1595
|
+
console.log(colors.green + "\\n✓ Financial Health Check Passed! All vouchers in exact balance." + colors.reset);
|
|
1596
|
+
} else {
|
|
1597
|
+
console.log(colors.red + \`\\n⚠ Double-entry violations found in \${audit.violatingVouchers.length} vouchers!\` + colors.reset);
|
|
1598
|
+
}
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1601
|
+
case '4': {
|
|
1602
|
+
const service = new LedgerService();
|
|
1603
|
+
const tb = await service.getTrialBalance();
|
|
1604
|
+
console.table(tb.accounts);
|
|
1605
|
+
console.log(colors.green + \`Grand Debit: ৳ \${tb.grandDebit} | Grand Credit: ৳ \${tb.grandCredit} | Balanced: \${tb.isBalanced}\` + colors.reset);
|
|
1606
|
+
break;
|
|
1607
|
+
}
|
|
1608
|
+
case '5': {
|
|
1609
|
+
const service = new LedgerService();
|
|
1610
|
+
const bs = await service.getBalanceSheet();
|
|
1611
|
+
console.log(colors.cyan + \`\\nTotal Assets: ৳ \${bs.totalAssets} | Total Liab & Equity: ৳ \${bs.totalLiabilitiesAndEquity}\\n\` + colors.reset);
|
|
1612
|
+
break;
|
|
1613
|
+
}
|
|
1614
|
+
case '0': {
|
|
1615
|
+
await closeDatabase();
|
|
1616
|
+
rl.close();
|
|
1617
|
+
process.exit(0);
|
|
1618
|
+
}
|
|
1619
|
+
default:
|
|
1620
|
+
console.log('Invalid choice.');
|
|
1621
|
+
}
|
|
1622
|
+
} catch (err: any) {
|
|
1623
|
+
console.error(colors.red + 'CLI Error: ' + err.message + colors.reset);
|
|
1624
|
+
} finally {
|
|
1625
|
+
printMenu();
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
printMenu();
|
|
1630
|
+
`);
|
|
1631
|
+
// 13. Server bootstrapper (server.ts)
|
|
1632
|
+
addFile('server.ts', `import { Aero, Router, vite } from 'aerojs';
|
|
1633
|
+
import { appConfig } from './config/app.js';
|
|
1634
|
+
import { initDatabase } from './database/connection.js';
|
|
1635
|
+
import { registerApiRoutes } from './routes/api.js';
|
|
1636
|
+
import { registerWebRoutes } from './routes/web.js';
|
|
1637
|
+
import { registerAuthRoutes } from './routes/auth.js';
|
|
1638
|
+
|
|
1639
|
+
async function bootstrap() {
|
|
1640
|
+
// Initialize MySQL Connection Pool
|
|
1641
|
+
await initDatabase();
|
|
1642
|
+
console.log('⚡ MySQL Database connection pool online.');
|
|
1643
|
+
|
|
1644
|
+
const app = new Aero();
|
|
1645
|
+
|
|
1646
|
+
// Next.js style interactive error dashboard & diagnostics
|
|
1647
|
+
app.useDiagnostics();
|
|
1648
|
+
|
|
1649
|
+
// Inertia.js protocol configuration with Vite integration
|
|
1650
|
+
app.useInertia({
|
|
1651
|
+
version: '1.0',
|
|
1652
|
+
rootView: \`<!DOCTYPE html>
|
|
1653
|
+
<html lang="en">
|
|
1654
|
+
<head>
|
|
1655
|
+
<meta charset="UTF-8">
|
|
1656
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1657
|
+
<title>Nursery ERP & Omnichannel E-Commerce</title>
|
|
1658
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
1659
|
+
\${vite('resources/js/app.ts')}
|
|
1660
|
+
</head>
|
|
1661
|
+
<body class="bg-slate-950 text-slate-100">
|
|
1662
|
+
@inertia
|
|
1663
|
+
</body>
|
|
1664
|
+
</html>\`
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
// Enable Zero-CDN OpenAPI 3.1 & Swagger Documentation
|
|
1668
|
+
app.useOpenAPI({
|
|
1669
|
+
title: 'Nursery ERP API',
|
|
1670
|
+
version: '1.0.0',
|
|
1671
|
+
description: 'High-Throughput Omnichannel Nursery & E-Commerce ERP API Specification',
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
// Mount Application Routes
|
|
1675
|
+
const router = new Router();
|
|
1676
|
+
registerApiRoutes(router);
|
|
1677
|
+
registerWebRoutes(router);
|
|
1678
|
+
registerAuthRoutes(router);
|
|
1679
|
+
|
|
1680
|
+
app.router.mount('/', router);
|
|
1681
|
+
|
|
1682
|
+
const port = appConfig.port;
|
|
1683
|
+
app.listen(port, () => {
|
|
1684
|
+
console.log(\`============================================================\`);
|
|
1685
|
+
console.log(\`🚀 Nursery ERP Server online at http://localhost:\${port}\`);
|
|
1686
|
+
console.log(\`📖 Interactive Swagger API Docs at http://localhost:\${port}/api/docs\`);
|
|
1687
|
+
console.log(\`⚡ Vue 3 + Inertia Admin Dashboard at http://localhost:\${port}/dashboard\`);
|
|
1688
|
+
console.log(\`============================================================\`);
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
bootstrap().catch((err) => {
|
|
1693
|
+
console.error('Fatal Server Boot Error:', err);
|
|
1694
|
+
process.exit(1);
|
|
1695
|
+
});
|
|
1696
|
+
`);
|
|
1697
|
+
// 14. Vitest integration test
|
|
1698
|
+
addFile('tests/erp.test.ts', `import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
1699
|
+
import { initDatabase, closeDatabase } from '../database/connection.js';
|
|
1700
|
+
import { LedgerService } from '../app/services/LedgerService.js';
|
|
1701
|
+
import { ProductService } from '../app/services/ProductService.js';
|
|
1702
|
+
|
|
1703
|
+
describe('Nursery ERP Full-Stack Integration Suite', () => {
|
|
1704
|
+
beforeAll(async () => {
|
|
1705
|
+
await initDatabase();
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
afterAll(async () => {
|
|
1709
|
+
await closeDatabase();
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1712
|
+
it('ProductService fetches product catalog', async () => {
|
|
1713
|
+
const service = new ProductService();
|
|
1714
|
+
const result = await service.paginate(1, 5);
|
|
1715
|
+
expect(result).toBeDefined();
|
|
1716
|
+
expect(Array.isArray(result.data)).toBe(true);
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
it('LedgerService double-entry audit verifies integrity', async () => {
|
|
1720
|
+
const service = new LedgerService();
|
|
1721
|
+
const audit = await service.auditIntegrityCheck();
|
|
1722
|
+
expect(audit).toBeDefined();
|
|
1723
|
+
expect(typeof audit.isClean).toBe('boolean');
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
it('LedgerService generates trial balance and balance sheet', async () => {
|
|
1727
|
+
const service = new LedgerService();
|
|
1728
|
+
const tb = await service.getTrialBalance();
|
|
1729
|
+
expect(tb).toBeDefined();
|
|
1730
|
+
expect(Array.isArray(tb.accounts)).toBe(true);
|
|
1731
|
+
|
|
1732
|
+
const bs = await service.getBalanceSheet();
|
|
1733
|
+
expect(bs).toBeDefined();
|
|
1734
|
+
expect(Array.isArray(bs.assets)).toBe(true);
|
|
1735
|
+
});
|
|
1736
|
+
});
|
|
1737
|
+
`);
|
|
1738
|
+
// 15. README.md
|
|
1739
|
+
addFile('README.md', `# ${projectName}
|
|
1740
|
+
|
|
1741
|
+
Omnichannel E-Commerce & Nursery ERP full-stack application built with **AeroJS**, **Vue 3**, **Inertia.js**, and **MySQL**.
|
|
1742
|
+
|
|
1743
|
+
## Quick Start
|
|
1744
|
+
\`\`\`bash
|
|
1745
|
+
# 1. Install dependencies
|
|
1746
|
+
npm install
|
|
1747
|
+
|
|
1748
|
+
# 2. Seed default Chart of Accounts
|
|
1749
|
+
npm run db:seed
|
|
1750
|
+
|
|
1751
|
+
# 3. Launch interactive CLI toolkit
|
|
1752
|
+
npm run cli
|
|
1753
|
+
|
|
1754
|
+
# 4. Start Development Server
|
|
1755
|
+
npm run dev
|
|
1756
|
+
\`\`\`
|
|
1757
|
+
`);
|
|
1758
|
+
return createdFiles;
|
|
1759
|
+
}
|
|
1760
|
+
//# sourceMappingURL=ecommerce-erp.js.map
|