mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-06-01 07:29:01 +00:00
wip
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
export enum WorkspacesError {
|
||||
WORKSPACE_NOT_FOUND = 'WORKSPACE_NOT_FOUND',
|
||||
NOT_WORKSPACE_OWNER = 'NOT_WORKSPACE_OWNER',
|
||||
WORKSPACE_DELETING = 'WORKSPACE_DELETING',
|
||||
CANNOT_DELETE_CURRENT_ORGANIZATION = 'CANNOT_DELETE_CURRENT_ORGANIZATION',
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from './dtos/WorkspaceResponse.dto';
|
||||
import { WorkspaceBuildJobResponseDto } from './dtos/WorkspaceBuildJobResponse.dto';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { WorkspacesError } from './Workspaces.constants';
|
||||
|
||||
@ApiTags('Workspaces')
|
||||
@Controller('workspaces')
|
||||
@@ -128,7 +129,7 @@ export class WorkspacesController {
|
||||
|
||||
if (organizationId === currentOrganizationId) {
|
||||
throw new ServiceError(
|
||||
'CANNOT_DELETE_CURRENT_ORGANIZATION',
|
||||
WorkspacesError.CANNOT_DELETE_CURRENT_ORGANIZATION,
|
||||
'Cannot delete the current organization',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,7 @@ import { UserTenant } from '@/modules/System/models/UserTenant.model';
|
||||
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||
import { TenantDBManager } from '@/modules/TenantDBManager/TenantDBManager';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
const ERRORS = {
|
||||
WORKSPACE_NOT_FOUND: 'WORKSPACE.NOT_FOUND',
|
||||
NOT_WORKSPACE_OWNER: 'NOT.WORKSPACE.OWNER',
|
||||
};
|
||||
import { WorkspacesError } from '../Workspaces.constants';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteWorkspaceService {
|
||||
@@ -33,14 +29,14 @@ export class DeleteWorkspaceService {
|
||||
const tenant = await this.tenantModel.query().findOne({ organizationId });
|
||||
|
||||
if (!tenant) {
|
||||
throw new ServiceError(ERRORS.WORKSPACE_NOT_FOUND);
|
||||
throw new ServiceError(WorkspacesError.WORKSPACE_NOT_FOUND);
|
||||
}
|
||||
const membership = await this.userTenantModel
|
||||
.query()
|
||||
.findOne({ userId, tenantId: tenant.id });
|
||||
|
||||
if (!membership || membership.role !== 'owner') {
|
||||
throw new ServiceError(ERRORS.NOT_WORKSPACE_OWNER);
|
||||
throw new ServiceError(WorkspacesError.NOT_WORKSPACE_OWNER);
|
||||
}
|
||||
// Drop the physical tenant database if it exists.
|
||||
await this.tenantDBManager.dropDatabaseIfExists();
|
||||
|
||||
@@ -10,12 +10,7 @@ import {
|
||||
DeleteWorkspaceQueueJobPayload,
|
||||
} from '../Workspaces.types';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
const ERRORS = {
|
||||
WORKSPACE_NOT_FOUND: 'WORKSPACE.NOT_FOUND',
|
||||
NOT_WORKSPACE_OWNER: 'NOT.WORKSPACE.OWNER',
|
||||
WORKSPACE_DELETING: 'WORKSPACE.DELETING',
|
||||
};
|
||||
import { WorkspacesError } from '../Workspaces.constants';
|
||||
|
||||
interface DeleteWorkspaceResult {
|
||||
jobId: string | number;
|
||||
@@ -51,7 +46,7 @@ export class DeleteWorkspaceJobService {
|
||||
const tenant = await this.tenantModel.query().findOne({ organizationId });
|
||||
|
||||
if (!tenant) {
|
||||
throw new ServiceError(ERRORS.WORKSPACE_NOT_FOUND);
|
||||
throw new ServiceError(WorkspacesError.WORKSPACE_NOT_FOUND);
|
||||
}
|
||||
|
||||
const membership = await this.userTenantModel
|
||||
@@ -59,12 +54,12 @@ export class DeleteWorkspaceJobService {
|
||||
.findOne({ userId, tenantId: tenant.id });
|
||||
|
||||
if (!membership || membership.role !== 'owner') {
|
||||
throw new ServiceError(ERRORS.NOT_WORKSPACE_OWNER);
|
||||
throw new ServiceError(WorkspacesError.NOT_WORKSPACE_OWNER);
|
||||
}
|
||||
|
||||
// Check if workspace is already being deleted.
|
||||
if (tenant.isDeleting) {
|
||||
throw new ServiceError(ERRORS.WORKSPACE_DELETING);
|
||||
throw new ServiceError(WorkspacesError.WORKSPACE_DELETING);
|
||||
}
|
||||
|
||||
// Emit workspace deleting event.
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { UserTenant } from '@/modules/System/models/UserTenant.model';
|
||||
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||
import { WorkspacesError } from '../Workspaces.constants';
|
||||
|
||||
@Injectable()
|
||||
export class InactivateWorkspaceService {
|
||||
@@ -23,20 +24,19 @@ export class InactivateWorkspaceService {
|
||||
const tenant = await this.tenantModel.query().findOne({ organizationId });
|
||||
|
||||
if (!tenant) {
|
||||
throw new ServiceError('WORKSPACE_NOT_FOUND', 'Workspace not found');
|
||||
throw new ServiceError(WorkspacesError.WORKSPACE_NOT_FOUND, 'Workspace not found');
|
||||
}
|
||||
|
||||
const membership = await this.userTenantModel
|
||||
.query()
|
||||
.findOne({ userId, tenantId: tenant.id })
|
||||
.withGraphFetched('tenant');
|
||||
|
||||
if (!membership) {
|
||||
throw new ServiceError('WORKSPACE_NOT_FOUND', 'Workspace not found');
|
||||
throw new ServiceError(WorkspacesError.WORKSPACE_NOT_FOUND, 'Workspace not found');
|
||||
}
|
||||
if (membership.role !== 'owner') {
|
||||
throw new ServiceError(
|
||||
'NOT_OWNER',
|
||||
WorkspacesError.NOT_WORKSPACE_OWNER,
|
||||
'Only the workspace owner can inactivate the workspace',
|
||||
);
|
||||
}
|
||||
@@ -58,7 +58,7 @@ export class InactivateWorkspaceService {
|
||||
const tenant = await this.tenantModel.query().findOne({ organizationId });
|
||||
|
||||
if (!tenant) {
|
||||
throw new ServiceError('WORKSPACE_NOT_FOUND', 'Workspace not found');
|
||||
throw new ServiceError(WorkspacesError.WORKSPACE_NOT_FOUND, 'Workspace not found');
|
||||
}
|
||||
|
||||
const membership = await this.userTenantModel
|
||||
@@ -67,12 +67,12 @@ export class InactivateWorkspaceService {
|
||||
.withGraphFetched('tenant');
|
||||
|
||||
if (!membership) {
|
||||
throw new ServiceError('WORKSPACE_NOT_FOUND', 'Workspace not found');
|
||||
throw new ServiceError(WorkspacesError.WORKSPACE_NOT_FOUND, 'Workspace not found');
|
||||
}
|
||||
|
||||
if (membership.role !== 'owner') {
|
||||
throw new ServiceError(
|
||||
'NOT_OWNER',
|
||||
WorkspacesError.NOT_WORKSPACE_OWNER,
|
||||
'Only the workspace owner can reactivate the workspace',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@ export class SetDefaultWorkspaceService {
|
||||
constructor(
|
||||
@Inject(UserTenant.name)
|
||||
private readonly userTenantModel: typeof UserTenant,
|
||||
|
||||
@Inject(SystemUser.name)
|
||||
private readonly systemUserModel: typeof SystemUser,
|
||||
|
||||
@Inject(TenantModel.name)
|
||||
private readonly tenantModel: typeof TenantModel,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||
import { WorkspaceDto } from '../dtos/WorkspaceResponse.dto';
|
||||
import { WorkspaceTransformer } from '../transformers/WorkspaceTransformer';
|
||||
import { GetWorkspacesFinancialService } from './GetWorkspacesFinancial.service';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
|
||||
@Injectable()
|
||||
export class GetWorkspacesService {
|
||||
@@ -13,6 +14,7 @@ export class GetWorkspacesService {
|
||||
@Inject(SystemUser.name)
|
||||
private readonly systemUserModel: typeof SystemUser,
|
||||
private readonly financialService: GetWorkspacesFinancialService,
|
||||
private readonly transformer: TransformerInjectable,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -50,30 +52,15 @@ export class GetWorkspacesService {
|
||||
const financialDataMap =
|
||||
await this.financialService.getWorkspacesFinancial(workspaceInfos);
|
||||
|
||||
const transformer = new WorkspaceTransformer();
|
||||
let workspaces = memberships.map((membership) => {
|
||||
const financialData = financialDataMap.get(membership.tenantId);
|
||||
return transformer.transform(
|
||||
membership,
|
||||
return this.transformer.transform(
|
||||
memberships,
|
||||
new WorkspaceTransformer(),
|
||||
{
|
||||
defaultTenantId,
|
||||
financialData,
|
||||
);
|
||||
});
|
||||
|
||||
// Filter out inactive workspaces unless includeInactive is true
|
||||
if (!includeInactive) {
|
||||
workspaces = workspaces.filter((w) => w.isActive);
|
||||
}
|
||||
|
||||
// Sort: current organization first, then by name
|
||||
return workspaces.sort((a, b) => {
|
||||
if (currentOrganizationId) {
|
||||
if (a.organizationId === currentOrganizationId) return -1;
|
||||
if (b.organizationId === currentOrganizationId) return 1;
|
||||
}
|
||||
return (a.metadata?.name || a.organizationId).localeCompare(
|
||||
b.metadata?.name || b.organizationId,
|
||||
);
|
||||
});
|
||||
financialDataMap,
|
||||
includeInactive,
|
||||
currentOrganizationId,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,6 @@ interface FinancialData {
|
||||
* Transforms UserTenant (workspace membership) to WorkspaceDto.
|
||||
*/
|
||||
export class WorkspaceTransformer extends Transformer<UserTenant> {
|
||||
private defaultTenantId?: number;
|
||||
private financialData?: FinancialData;
|
||||
|
||||
/**
|
||||
* Include these attributes in the transformed output.
|
||||
*/
|
||||
@@ -29,7 +26,6 @@ export class WorkspaceTransformer extends Transformer<UserTenant> {
|
||||
'isDeleting',
|
||||
'isActive',
|
||||
'buildJobId',
|
||||
'role',
|
||||
'metadata',
|
||||
'isDefault',
|
||||
'totalIncome',
|
||||
@@ -104,44 +100,48 @@ export class WorkspaceTransformer extends Transformer<UserTenant> {
|
||||
* Determine if this workspace is the user's default.
|
||||
*/
|
||||
protected isDefault = (membership: UserTenant): boolean => {
|
||||
if (!this.defaultTenantId) return false;
|
||||
return membership.tenantId === this.defaultTenantId;
|
||||
const defaultTenantId = this.options?.defaultTenantId;
|
||||
if (!defaultTenantId) return false;
|
||||
return membership.tenantId === defaultTenantId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get total income from financial data.
|
||||
*/
|
||||
protected totalIncome = (): number => {
|
||||
return this.financialData?.totalIncome ?? 0;
|
||||
protected totalIncome = (financialData?: FinancialData): number => {
|
||||
return financialData?.totalIncome ?? 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get total expenses from financial data.
|
||||
*/
|
||||
protected totalExpenses = (): number => {
|
||||
return this.financialData?.totalExpenses ?? 0;
|
||||
protected totalExpenses = (financialData?: FinancialData): number => {
|
||||
return financialData?.totalExpenses ?? 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get total assets from financial data.
|
||||
*/
|
||||
protected totalAssets = (): number => {
|
||||
return this.financialData?.totalAssets ?? 0;
|
||||
protected totalAssets = (financialData?: FinancialData): number => {
|
||||
return financialData?.totalAssets ?? 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get total liabilities from financial data.
|
||||
*/
|
||||
protected totalLiabilities = (): number => {
|
||||
return this.financialData?.totalLiabilities ?? 0;
|
||||
protected totalLiabilities = (financialData?: FinancialData): number => {
|
||||
return financialData?.totalLiabilities ?? 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get formatted total assets.
|
||||
*/
|
||||
protected formattedTotalAssets = (membership: UserTenant): string => {
|
||||
protected formattedTotalAssets = (
|
||||
membership: UserTenant,
|
||||
financialData?: FinancialData,
|
||||
): string => {
|
||||
const currencyCode = membership.tenant?.metadata?.baseCurrency;
|
||||
return formatNumber(this.totalAssets(), {
|
||||
return formatNumber(financialData?.totalAssets ?? 0, {
|
||||
currencyCode,
|
||||
money: true,
|
||||
});
|
||||
@@ -150,9 +150,12 @@ export class WorkspaceTransformer extends Transformer<UserTenant> {
|
||||
/**
|
||||
* Get formatted total liabilities.
|
||||
*/
|
||||
protected formattedTotalLiabilities = (membership: UserTenant): string => {
|
||||
protected formattedTotalLiabilities = (
|
||||
membership: UserTenant,
|
||||
financialData?: FinancialData,
|
||||
): string => {
|
||||
const currencyCode = membership.tenant?.metadata?.baseCurrency;
|
||||
return formatNumber(this.totalLiabilities(), {
|
||||
return formatNumber(financialData?.totalLiabilities ?? 0, {
|
||||
currencyCode,
|
||||
money: true,
|
||||
});
|
||||
@@ -161,13 +164,11 @@ export class WorkspaceTransformer extends Transformer<UserTenant> {
|
||||
/**
|
||||
* Transform single membership to WorkspaceDto.
|
||||
*/
|
||||
transform = (
|
||||
membership: UserTenant,
|
||||
defaultTenantId?: number,
|
||||
financialData?: FinancialData,
|
||||
): WorkspaceDto => {
|
||||
this.defaultTenantId = defaultTenantId;
|
||||
this.financialData = financialData;
|
||||
transform = (membership: UserTenant): WorkspaceDto => {
|
||||
const financialData = (
|
||||
this.options?.financialDataMap as Map<number, FinancialData>
|
||||
)?.get(membership.tenantId);
|
||||
|
||||
return {
|
||||
organizationId: this.organizationId(membership),
|
||||
isReady: this.isReady(membership),
|
||||
@@ -178,12 +179,51 @@ export class WorkspaceTransformer extends Transformer<UserTenant> {
|
||||
role: membership.role,
|
||||
isDefault: this.isDefault(membership),
|
||||
metadata: this.metadata(membership),
|
||||
totalIncome: this.totalIncome(),
|
||||
totalExpenses: this.totalExpenses(),
|
||||
totalAssets: this.totalAssets(),
|
||||
totalLiabilities: this.totalLiabilities(),
|
||||
formattedTotalAssets: this.formattedTotalAssets(membership),
|
||||
formattedTotalLiabilities: this.formattedTotalLiabilities(membership),
|
||||
totalIncome: this.totalIncome(financialData),
|
||||
totalExpenses: this.totalExpenses(financialData),
|
||||
totalAssets: this.totalAssets(financialData),
|
||||
totalLiabilities: this.totalLiabilities(financialData),
|
||||
formattedTotalAssets: this.formattedTotalAssets(membership, financialData),
|
||||
formattedTotalLiabilities: this.formattedTotalLiabilities(
|
||||
membership,
|
||||
financialData,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Process collections directly through transform, then apply
|
||||
* post-collection filtering and sorting.
|
||||
*/
|
||||
public work = (object: any) => {
|
||||
if (Array.isArray(object)) {
|
||||
const transformed = object.map((item) => this.transform(item));
|
||||
return this.postCollectionTransform(transformed);
|
||||
}
|
||||
return this.transform(object);
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter and sort the transformed workspaces collection.
|
||||
*/
|
||||
protected postCollectionTransform = (
|
||||
workspaces: WorkspaceDto[],
|
||||
): WorkspaceDto[] => {
|
||||
let result = workspaces;
|
||||
|
||||
if (!this.options?.includeInactive) {
|
||||
result = result.filter((w) => w.isActive);
|
||||
}
|
||||
|
||||
return result.sort((a, b) => {
|
||||
const currentOrganizationId = this.options?.currentOrganizationId as string;
|
||||
if (currentOrganizationId) {
|
||||
if (a.organizationId === currentOrganizationId) return -1;
|
||||
if (b.organizationId === currentOrganizationId) return 1;
|
||||
}
|
||||
return (a.metadata?.name || a.organizationId).localeCompare(
|
||||
b.metadata?.name || b.organizationId,
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user