diff --git a/packages/server/src/modules/Bills/commands/BillsValidators.service.ts b/packages/server/src/modules/Bills/commands/BillsValidators.service.ts index 86951d46a..1dd85897e 100644 --- a/packages/server/src/modules/Bills/commands/BillsValidators.service.ts +++ b/packages/server/src/modules/Bills/commands/BillsValidators.service.ts @@ -2,7 +2,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { ERRORS } from '../Bills.constants'; import { Bill } from '../models/Bill'; import { ServiceError } from '@/modules/Items/ServiceError'; -import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types'; import { Item } from '@/modules/Items/models/Item'; import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry'; import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost'; diff --git a/packages/server/src/modules/Organization/Organization.controller.ts b/packages/server/src/modules/Organization/Organization.controller.ts index 6fcca3d98..b4863ba0e 100644 --- a/packages/server/src/modules/Organization/Organization.controller.ts +++ b/packages/server/src/modules/Organization/Organization.controller.ts @@ -1,4 +1,10 @@ -import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBody, + ApiOkResponse, +} from '@nestjs/swagger'; import { Controller, Post, @@ -9,6 +15,7 @@ import { Res, Next, HttpCode, + Param, } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { BuildOrganizationService } from './commands/BuildOrganization.service'; @@ -20,6 +27,7 @@ import { GetCurrentOrganizationService } from './queries/GetCurrentOrganization. import { UpdateOrganizationService } from './commands/UpdateOrganization.service'; import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard'; import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards'; +import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service'; @ApiTags('Organization') @Controller('organization') @@ -30,6 +38,7 @@ export class OrganizationController { private readonly buildOrganizationService: BuildOrganizationService, private readonly getCurrentOrgService: GetCurrentOrganizationService, private readonly updateOrganizationService: UpdateOrganizationService, + private readonly getBuildOrganizationJobService: GetBuildOrganizationBuildJob, ) {} @Post('build') @@ -51,6 +60,13 @@ export class OrganizationController { }; } + @Get('build/:buildJobId') + @HttpCode(200) + @ApiOperation({ summary: 'Gets the organization build job details' }) + async buildJob(@Param('buildJobId') buildJobId: string) { + return this.getBuildOrganizationJobService.getJobDetails(buildJobId); + } + @Get('current') @HttpCode(200) @ApiOperation({ summary: 'Get current organization' }) diff --git a/packages/server/src/modules/Organization/Organization.module.ts b/packages/server/src/modules/Organization/Organization.module.ts index 8987b29af..271f3a75a 100644 --- a/packages/server/src/modules/Organization/Organization.module.ts +++ b/packages/server/src/modules/Organization/Organization.module.ts @@ -12,6 +12,7 @@ import { TenantDBManagerModule } from '../TenantDBManager/TenantDBManager.module import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service'; import { SyncSystemUserToTenantService } from './commands/SyncSystemUserToTenant.service'; import { SyncSystemUserToTenantSubscriber } from './subscribers/SyncSystemUserToTenant.subscriber'; +import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service'; @Module({ providers: [ @@ -23,7 +24,8 @@ import { SyncSystemUserToTenantSubscriber } from './subscribers/SyncSystemUserTo CommandOrganizationValidators, OrganizationBaseCurrencyLocking, SyncSystemUserToTenantService, - SyncSystemUserToTenantSubscriber + SyncSystemUserToTenantSubscriber, + GetBuildOrganizationBuildJob ], imports: [ BullModule.registerQueue({ name: OrganizationBuildQueue }), diff --git a/packages/server/src/modules/Organization/Organization.types.ts b/packages/server/src/modules/Organization/Organization.types.ts index 762fd0fe3..8c84a133f 100644 --- a/packages/server/src/modules/Organization/Organization.types.ts +++ b/packages/server/src/modules/Organization/Organization.types.ts @@ -58,3 +58,9 @@ export const OrganizationBuildQueueJob = 'OrganizationBuildQueueJob'; export interface OrganizationBuildQueueJobPayload extends TenantJobPayload { buildDto: BuildOrganizationDto; } + +export interface BuildOrganizationResult { + delay: number; + processedOn: number; + jobId: string; +} diff --git a/packages/server/src/modules/Organization/commands/BuildOrganization.service.ts b/packages/server/src/modules/Organization/commands/BuildOrganization.service.ts index 1d0c9e0ed..e7bd5784c 100644 --- a/packages/server/src/modules/Organization/commands/BuildOrganization.service.ts +++ b/packages/server/src/modules/Organization/commands/BuildOrganization.service.ts @@ -1,13 +1,14 @@ import { Queue } from 'bullmq'; import { InjectQueue } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; import { + BuildOrganizationResult, IOrganizationBuildEventPayload, IOrganizationBuiltEventPayload, OrganizationBuildQueue, OrganizationBuildQueueJob, OrganizationBuildQueueJobPayload, } from '../Organization.types'; -import { Injectable } from '@nestjs/common'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { throwIfTenantInitizalized, @@ -48,7 +49,7 @@ export class BuildOrganizationService { await this.tenantsManager.createDatabase(); await this.tenantsManager.migrateTenant(); - await this.tenantsManager.seedTenant() + await this.tenantsManager.seedTenant(); // Throws `onOrganizationBuild` event. await this.eventPublisher.emitAsync(events.organization.build, { @@ -77,7 +78,7 @@ export class BuildOrganizationService { */ async buildRunJob( buildDTO: BuildOrganizationDto, - ): Promise<{ nextRunAt: Date; jobId: string }> { + ): Promise { const tenant = await this.tenancyContext.getTenant(); const systemUser = await this.tenancyContext.getSystemUser(); @@ -106,8 +107,9 @@ export class BuildOrganizationService { await this.tenantRepository.markAsBuilding(jobMeta.id).findById(tenant.id); return { - nextRunAt: jobMeta.data.nextRunAt, - jobId: jobMeta.data.id, + delay: jobMeta.delay, + processedOn: jobMeta.processedOn, + jobId: jobMeta.id, }; } diff --git a/packages/server/src/modules/Organization/commands/GetBuildOrganizationJob.service.ts b/packages/server/src/modules/Organization/commands/GetBuildOrganizationJob.service.ts new file mode 100644 index 000000000..56312717f --- /dev/null +++ b/packages/server/src/modules/Organization/commands/GetBuildOrganizationJob.service.ts @@ -0,0 +1,37 @@ +import { Queue } from 'bullmq'; +import { Injectable } from '@nestjs/common'; +import { InjectQueue } from '@nestjs/bullmq'; +import { OrganizationBuildQueue } from '../Organization.types'; +import { ServiceError } from '@/modules/Items/ServiceError'; + +@Injectable() +export class GetBuildOrganizationBuildJob { + constructor( + @InjectQueue(OrganizationBuildQueue) + private readonly organizationBuildQueue: Queue, + ) {} + + /** + * Gets the build job details by job ID. + * @param {string} jobId - The ID of the job to retrieve. + * @returns {Promise} - Returns the job details. + */ + async getJobDetails(jobId: string): Promise { + const job = await this.organizationBuildQueue.getJob(jobId); + + if (!job) { + throw new ServiceError('Job not found', 'JOB.NOT_FOUND'); + } + const state = await job.getState(); + + return { + id: job.id, + state, + progress: job.progress, + isCompleted: state === 'completed', + isRunning: state === 'active', + isWaiting: state === 'waiting' || state === 'waiting-children', + isFailed: state === 'failed', + }; + } +} diff --git a/packages/webapp/src/containers/Setup/SetupInitializingForm.tsx b/packages/webapp/src/containers/Setup/SetupInitializingForm.tsx index a348a4876..85a816670 100644 --- a/packages/webapp/src/containers/Setup/SetupInitializingForm.tsx +++ b/packages/webapp/src/containers/Setup/SetupInitializingForm.tsx @@ -25,7 +25,7 @@ function SetupInitializingForm({ const [isJobDone, setIsJobDone] = React.useState(false); const { - data: { running, queued, failed, completed }, + data: { isRunning, isWaiting, isFailed, isCompleted }, isFetching: isJobFetching, } = useJob(organization?.build_job_id, { refetchInterval: 2000, @@ -33,11 +33,11 @@ function SetupInitializingForm({ }); React.useEffect(() => { - if (completed) { + if (isCompleted) { refetch(); setIsJobDone(true); } - }, [refetch, completed, setOrganizationSetupCompleted]); + }, [refetch, isCompleted, setOrganizationSetupCompleted]); React.useEffect(() => { if (isSuccess && isJobDone) { @@ -48,11 +48,11 @@ function SetupInitializingForm({ return (
- {failed ? ( + {isFailed ? ( - ) : running || queued || isJobFetching ? ( + ) : isRunning || isWaiting || isJobFetching ? ( - ) : completed ? ( + ) : isCompleted ? ( ) : ( diff --git a/packages/webapp/src/hooks/query/jobs.tsx b/packages/webapp/src/hooks/query/jobs.tsx index 37fd7dab6..85cf27eeb 100644 --- a/packages/webapp/src/hooks/query/jobs.tsx +++ b/packages/webapp/src/hooks/query/jobs.tsx @@ -1,4 +1,5 @@ // @ts-nocheck +import { transformToCamelCase } from '@/utils'; import { useRequestQuery } from '../useQueryRequest'; /** @@ -7,9 +8,9 @@ import { useRequestQuery } from '../useQueryRequest'; export function useJob(jobId, props = {}) { return useRequestQuery( ['JOB', jobId], - { method: 'get', url: `jobs/${jobId}` }, + { method: 'get', url: `organization/build/${jobId}` }, { - select: (res) => res.data.job, + select: (res) => transformToCamelCase(res.data), defaultData: {}, ...props, },