fixed update space model

This commit is contained in:
hannathkadher
2025-02-25 01:37:23 +04:00
parent acc8f3ef4d
commit 67ce73754b
6 changed files with 566 additions and 388 deletions

View File

@ -1,2 +1,3 @@
export * from './update-subspace.interface'; export * from './update-subspace.interface';
export * from './modify-subspace.interface'; export * from './modify-subspace.interface';
export * from './single-subspace.interface';

View File

@ -0,0 +1,9 @@
import { SubspaceModelEntity } from '@app/common/modules/space-model';
import { ModifyTagModelDto } from '../dtos';
import { ModifyAction } from '@app/common/constants/modify-action.enum';
export interface ISingleSubspaceModel {
subspaceModel: SubspaceModelEntity;
action: ModifyAction;
tags: ModifyTagModelDto[];
}

View File

@ -5,10 +5,11 @@ import {
SpaceModelEntity, SpaceModelEntity,
SpaceModelProductAllocationEntity, SpaceModelProductAllocationEntity,
SpaceModelProductAllocationRepoitory, SpaceModelProductAllocationRepoitory,
SubspaceModelProductAllocationEntity,
} from '@app/common/modules/space-model'; } from '@app/common/modules/space-model';
import { TagService as NewTagService } from 'src/tags/services'; import { TagService as NewTagService } from 'src/tags/services';
import { ProcessTagDto } from 'src/tags/dtos'; import { ProcessTagDto } from 'src/tags/dtos';
import { ModifyTagModelDto } from '../dtos'; import { ModifySubspaceModelDto, ModifyTagModelDto } from '../dtos';
import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { NewTagEntity } from '@app/common/modules/tag'; import { NewTagEntity } from '@app/common/modules/tag';
import { ProductEntity } from '@app/common/modules/product/entities'; import { ProductEntity } from '@app/common/modules/product/entities';
@ -25,30 +26,83 @@ export class SpaceModelProductAllocationService {
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
tags: ProcessTagDto[], tags: ProcessTagDto[],
queryRunner?: QueryRunner, queryRunner?: QueryRunner,
modifySubspaceModels?: ModifySubspaceModelDto[],
): Promise<SpaceModelProductAllocationEntity[]> { ): Promise<SpaceModelProductAllocationEntity[]> {
try { try {
if (!tags.length) return [];
const processedTags = await this.tagService.processTags( const processedTags = await this.tagService.processTags(
tags, tags,
projectUuid, projectUuid,
queryRunner, queryRunner,
); );
const productAllocations: SpaceModelProductAllocationEntity[] = []; const productAllocations: SpaceModelProductAllocationEntity[] = [];
const existingAllocations = new Map<
string,
SpaceModelProductAllocationEntity
>();
for (const tag of processedTags) { for (const tag of processedTags) {
let allocation = await this.getAllocationByProduct( let isTagNeeded = true;
tag.product,
spaceModel,
queryRunner,
);
if (!allocation) { if (modifySubspaceModels) {
allocation = this.createNewAllocation(spaceModel, tag, queryRunner); const relatedSubspaces = await queryRunner.manager.find(
productAllocations.push(allocation); SubspaceModelProductAllocationEntity,
} else if ( {
!allocation.tags.some((existingTag) => existingTag.uuid === tag.uuid) where: {
) { product: tag.product,
allocation.tags.push(tag); subspaceModel: { spaceModel: { uuid: spaceModel.uuid } },
await this.saveAllocation(allocation, queryRunner); tags: { uuid: tag.uuid },
},
relations: ['subspaceModel', 'tags'],
},
);
for (const subspaceWithTag of relatedSubspaces) {
const modifyingSubspace = modifySubspaceModels.find(
(subspace) =>
subspace.action === ModifyAction.UPDATE &&
subspace.uuid === subspaceWithTag.subspaceModel.uuid,
);
if (
modifyingSubspace &&
modifyingSubspace.tags &&
modifyingSubspace.tags.some(
(subspaceTag) =>
subspaceTag.action === ModifyAction.DELETE &&
subspaceTag.tagUuid === tag.uuid,
)
) {
isTagNeeded = true;
break;
}
}
}
if (isTagNeeded) {
await this.validateTagWithinSpaceModel(queryRunner, tag, spaceModel);
let allocation = existingAllocations.get(tag.product.uuid);
if (!allocation) {
allocation = await this.getAllocationByProduct(
tag.product,
spaceModel,
queryRunner,
);
if (allocation) {
existingAllocations.set(tag.product.uuid, allocation);
}
}
if (!allocation) {
allocation = this.createNewAllocation(spaceModel, tag, queryRunner);
productAllocations.push(allocation);
} else if (!allocation.tags.some((t) => t.uuid === tag.uuid)) {
allocation.tags.push(tag);
await this.saveAllocation(allocation, queryRunner);
}
} }
} }
@ -67,10 +121,17 @@ export class SpaceModelProductAllocationService {
projectUuid: string, projectUuid: string,
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
modifySubspaceModels?: ModifySubspaceModelDto[],
): Promise<void> { ): Promise<void> {
try { try {
await Promise.all([ await Promise.all([
this.processAddActions(dtos, projectUuid, spaceModel, queryRunner), this.processAddActions(
dtos,
projectUuid,
spaceModel,
queryRunner,
modifySubspaceModels,
),
this.processDeleteActions(dtos, queryRunner), this.processDeleteActions(dtos, queryRunner),
]); ]);
} catch (error) { } catch (error) {
@ -83,6 +144,7 @@ export class SpaceModelProductAllocationService {
projectUuid: string, projectUuid: string,
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
modifySubspaceModels?: ModifySubspaceModelDto[],
): Promise<void> { ): Promise<void> {
const addDtos: ProcessTagDto[] = dtos const addDtos: ProcessTagDto[] = dtos
.filter((dto) => dto.action === ModifyAction.ADD) .filter((dto) => dto.action === ModifyAction.ADD)
@ -98,6 +160,7 @@ export class SpaceModelProductAllocationService {
spaceModel, spaceModel,
addDtos, addDtos,
queryRunner, queryRunner,
modifySubspaceModels,
); );
} }
} }
@ -172,40 +235,80 @@ export class SpaceModelProductAllocationService {
private async processDeleteActions( private async processDeleteActions(
dtos: ModifyTagModelDto[], dtos: ModifyTagModelDto[],
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { ): Promise<SpaceModelProductAllocationEntity[]> {
const deleteDtos = dtos.filter((dto) => dto.action === ModifyAction.DELETE); try {
if (!dtos || dtos.length === 0) {
throw new Error('No DTOs provided for deletion.');
}
await Promise.all( const tagUuidsToDelete = dtos
deleteDtos.map(async (dto) => { .filter((dto) => dto.action === ModifyAction.DELETE && dto.tagUuid)
if (!dto.tagUuid) return; .map((dto) => dto.tagUuid);
const allocation = await queryRunner.manager.findOne( if (tagUuidsToDelete.length === 0) return [];
SpaceModelProductAllocationEntity,
{ const allocationsToUpdate = await queryRunner.manager.find(
where: { tags: { uuid: dto.tagUuid } }, SpaceModelProductAllocationEntity,
relations: ['tags'], {
}, where: { tags: { uuid: In(tagUuidsToDelete) } },
relations: ['tags'],
},
);
if (!allocationsToUpdate || allocationsToUpdate.length === 0) return [];
const deletedAllocations: SpaceModelProductAllocationEntity[] = [];
const allocationUpdates: SpaceModelProductAllocationEntity[] = [];
for (const allocation of allocationsToUpdate) {
const updatedTags = allocation.tags.filter(
(tag) => !tagUuidsToDelete.includes(tag.uuid),
); );
if (!allocation) { if (updatedTags.length === allocation.tags.length) {
throw new HttpException( continue;
`Allocation with tag ${dto.tagUuid} not found`,
HttpStatus.NOT_FOUND,
);
} }
const existingTag = allocation.tags.find( if (updatedTags.length === 0) {
(tag) => tag.uuid === dto.tagUuid, deletedAllocations.push(allocation);
); } else {
if (!existingTag) return; allocation.tags = updatedTags;
allocationUpdates.push(allocation);
}
}
await queryRunner.manager if (allocationUpdates.length > 0) {
.createQueryBuilder() await queryRunner.manager.save(
.relation(SpaceModelProductAllocationEntity, 'tags') SpaceModelProductAllocationEntity,
.of(allocation) allocationUpdates,
.remove(existingTag); );
}), }
);
if (deletedAllocations.length > 0) {
await queryRunner.manager.remove(
SpaceModelProductAllocationEntity,
deletedAllocations,
);
}
await queryRunner.manager
.createQueryBuilder()
.delete()
.from('space_model_product_tags')
.where(
'space_model_product_allocation_id NOT IN ' +
queryRunner.manager
.createQueryBuilder()
.select('uuid')
.from(SpaceModelProductAllocationEntity, 'allocation')
.getQuery(),
)
.execute();
return deletedAllocations;
} catch (error) {
throw this.handleError(error, `Failed to delete tags in space model`);
}
} }
private async getAllocationByTagUuid( private async getAllocationByTagUuid(
@ -217,4 +320,35 @@ export class SpaceModelProductAllocationService {
relations: ['tags'], relations: ['tags'],
}); });
} }
private async validateTagWithinSpaceModel(
queryRunner: QueryRunner,
tag: NewTagEntity,
spaceModel: SpaceModelEntity,
) {
const existingAllocationsForProduct = await queryRunner.manager.find(
SpaceModelProductAllocationEntity,
{
where: { spaceModel, product: tag.product },
relations: ['tags'],
},
);
//Flatten all existing tags for this product in the space
const existingTagsForProduct = existingAllocationsForProduct.flatMap(
(allocation) => allocation.tags,
);
// Check if the tag is already assigned to the same product in this subspace
const isDuplicateTag = existingTagsForProduct.some(
(existingTag) => existingTag.uuid === tag.uuid,
);
if (isDuplicateTag) {
throw new HttpException(
`Tag ${tag.uuid} is already allocated to product ${tag.product.uuid} within this space (${spaceModel.uuid}).`,
HttpStatus.BAD_REQUEST,
);
}
}
} }

View File

@ -223,6 +223,7 @@ export class SpaceModelService {
param.projectUuid, param.projectUuid,
spaceModel, spaceModel,
queryRunner, queryRunner,
dto.subspaceModels,
); );
} }
await queryRunner.commitTransaction(); await queryRunner.commitTransaction();
@ -255,14 +256,12 @@ export class SpaceModelService {
const spaceModel = await this.validateSpaceModel(param.spaceModelUuid); const spaceModel = await this.validateSpaceModel(param.spaceModelUuid);
if (spaceModel.subspaceModels?.length) { if (spaceModel.subspaceModels?.length) {
const deleteSubspaceDtos = spaceModel.subspaceModels.map( const deleteSubspaceUuids = spaceModel.subspaceModels.map(
(subspace) => ({ (subspace) => subspace.uuid,
subspaceUuid: subspace.uuid,
}),
); );
await this.subSpaceModelService.deleteSubspaceModels( await this.subSpaceModelService.clearModels(
deleteSubspaceDtos, deleteSubspaceUuids,
queryRunner, queryRunner,
); );
} }

View File

@ -9,10 +9,8 @@ import {
} from '@app/common/modules/space-model'; } from '@app/common/modules/space-model';
import { NewTagEntity } from '@app/common/modules/tag'; import { NewTagEntity } from '@app/common/modules/tag';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { import { ModifyTagModelDto } from 'src/space-model/dtos';
ModifySubspaceModelDto, import { ISingleSubspaceModel } from 'src/space-model/interfaces';
ModifyTagModelDto,
} from 'src/space-model/dtos';
import { ProcessTagDto } from 'src/tags/dtos'; import { ProcessTagDto } from 'src/tags/dtos';
import { TagService as NewTagService } from 'src/tags/services'; import { TagService as NewTagService } from 'src/tags/services';
import { In, QueryRunner } from 'typeorm'; import { In, QueryRunner } from 'typeorm';
@ -179,225 +177,6 @@ export class SubspaceModelProductAllocationService {
} }
} }
async processAddActions(
dtos: ModifyTagModelDto[],
projectUuid: string,
subspaceModel: SubspaceModelEntity,
otherSubspaceModelUpdateDtos: ModifySubspaceModelDto[],
queryRunner: QueryRunner,
spaceModel: SpaceModelEntity,
spaceTagUpdateDtos?: ModifyTagModelDto[],
): Promise<{
updatedSubspaceDtos: ModifySubspaceModelDto[];
addedAllocations: SubspaceModelProductAllocationEntity[];
}> {
let addedAllocations: SubspaceModelProductAllocationEntity[] = [];
const updatedAllocations: SubspaceModelProductAllocationEntity[] = [];
const spaceAllocationToExclude: SpaceModelProductAllocationEntity[] = [];
const addDtos: ProcessTagDto[] = dtos
.filter((dto) => dto.action === ModifyAction.ADD)
.map((dto) => ({
name: dto.name,
productUuid: dto.productUuid,
uuid: dto.newTagUuid,
}));
if (addDtos.length > 0) {
let processedTags = await this.tagService.processTags(
addDtos,
projectUuid,
queryRunner,
);
for (const subspaceDto of otherSubspaceModelUpdateDtos) {
if (subspaceDto.action === ModifyAction.UPDATE && subspaceDto.tags) {
// Tag is deleted from one subspace and added in another subspace
const deletedTags = subspaceDto.tags.filter(
(tagDto) =>
tagDto.action === ModifyAction.DELETE &&
processedTags.some((tag) => tag.uuid === tagDto.tagUuid),
);
for (const deletedTag of deletedTags) {
const allocation = await queryRunner.manager.findOne(
SubspaceModelProductAllocationEntity,
{
where: {
subspaceModel: { uuid: subspaceDto.uuid },
},
relations: ['tags', 'product', 'subspace'],
},
);
const isCommonTag = allocation.tags.some(
(tag) => tag.uuid === deletedTag.tagUuid,
);
if (allocation && isCommonTag) {
const tagEntity = allocation.tags.find(
(tag) => tag.uuid === deletedTag.tagUuid,
);
allocation.tags = allocation.tags.filter(
(tag) => tag.uuid !== deletedTag.tagUuid,
);
await queryRunner.manager.save(allocation);
const productAllocationExistInSubspace =
await queryRunner.manager.findOne(
SubspaceModelProductAllocationEntity,
{
where: {
subspaceModel: { uuid: subspaceDto.uuid },
product: { uuid: allocation.product.uuid },
},
relations: ['tags'],
},
);
if (productAllocationExistInSubspace) {
productAllocationExistInSubspace.tags.push(tagEntity);
await queryRunner.manager.save(
productAllocationExistInSubspace,
);
} else {
const newProductAllocation = queryRunner.manager.create(
SubspaceModelProductAllocationEntity,
{
subspaceModel: subspaceModel,
product: allocation.product,
tags: [deletedTag],
},
);
await queryRunner.manager.save(newProductAllocation);
}
updatedAllocations.push(allocation);
// Remove the tag from processedTags to prevent duplication
processedTags = processedTags.filter(
(tag) => tag.uuid !== deletedTag.tagUuid,
);
// Remove the tag from subspaceDto.tags to ensure it's not processed again while processing other dtos.
subspaceDto.tags = subspaceDto.tags.filter(
(tagDto) => tagDto.tagUuid !== deletedTag.tagUuid,
);
}
}
}
if (subspaceDto.action === ModifyAction.DELETE) {
const allocation = await queryRunner.manager.findOne(
SubspaceModelProductAllocationEntity,
{
where: { subspaceModel: { uuid: subspaceDto.uuid } },
relations: ['tags'],
},
);
const repeatedTags = allocation?.tags.filter((tag) =>
processedTags.some(
(processedTag) => processedTag.uuid === tag.uuid,
),
);
if (repeatedTags.length > 0) {
allocation.tags = allocation.tags.filter(
(tag) =>
!repeatedTags.some(
(repeatedTag) => repeatedTag.uuid === tag.uuid,
),
);
await queryRunner.manager.save(allocation);
const productAllocationExistInSubspace =
await queryRunner.manager.findOne(
SubspaceModelProductAllocationEntity,
{
where: {
subspaceModel: { uuid: subspaceDto.uuid },
product: { uuid: allocation.product.uuid },
},
relations: ['tags'],
},
);
if (productAllocationExistInSubspace) {
productAllocationExistInSubspace.tags.push(...repeatedTags);
await queryRunner.manager.save(productAllocationExistInSubspace);
} else {
const newProductAllocation = queryRunner.manager.create(
SubspaceModelProductAllocationEntity,
{
subspaceModel: subspaceModel,
product: allocation.product,
tags: repeatedTags,
},
);
await queryRunner.manager.save(newProductAllocation);
}
const newAllocation = queryRunner.manager.create(
SubspaceModelProductAllocationEntity,
{
subspaceModel: subspaceModel,
product: allocation.product,
tags: repeatedTags,
},
);
await queryRunner.manager.save(newAllocation);
}
}
}
if (spaceTagUpdateDtos) {
const deletedSpaceTags = spaceTagUpdateDtos.filter(
(tagDto) =>
tagDto.action === ModifyAction.DELETE &&
processedTags.some((tag) => tag.uuid === tagDto.tagUuid),
);
for (const deletedTag of deletedSpaceTags) {
const allocation = await queryRunner.manager.findOne(
SpaceModelProductAllocationEntity,
{
where: {
spaceModel: { uuid: spaceModel.uuid },
tags: { uuid: deletedTag.tagUuid },
},
relations: ['tags', 'subspace'],
},
);
if (
allocation &&
allocation.tags.some((tag) => tag.uuid === deletedTag.tagUuid)
) {
spaceAllocationToExclude.push(allocation);
}
}
}
// Create new product allocations
const newAllocations = await this.createProductAllocations(
subspaceModel,
processedTags,
queryRunner,
spaceAllocationToExclude,
);
addedAllocations = [...newAllocations, ...updatedAllocations]; // Combine newly created and updated allocations
}
return {
updatedSubspaceDtos: otherSubspaceModelUpdateDtos, // Updated subspace DTOs
addedAllocations, // Newly created allocations
};
}
async processDeleteActions( async processDeleteActions(
dtos: ModifyTagModelDto[], dtos: ModifyTagModelDto[],
queryRunner: QueryRunner, queryRunner: QueryRunner,
@ -477,42 +256,253 @@ export class SubspaceModelProductAllocationService {
} }
} }
async updateProductAllocations( async updateAllocations(
dtos: ModifyTagModelDto[], subspaceModels: ISingleSubspaceModel[],
projectUuid: string, projectUuid: string,
subspaceModel: SubspaceModelEntity,
otherSubspaceModelUpdateDtos: ModifySubspaceModelDto[],
spaceModel: SpaceModelEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
spaceModel: SpaceModelEntity,
spaceTagUpdateDtos?: ModifyTagModelDto[], spaceTagUpdateDtos?: ModifyTagModelDto[],
): Promise<{ ) {
addedAllocations: SubspaceModelProductAllocationEntity[]; const spaceAllocationToExclude: SpaceModelProductAllocationEntity[] = [];
deletedAllocations: SubspaceModelProductAllocationEntity[];
updatedSubspaceDtos: ModifySubspaceModelDto[]; for (const subspaceModel of subspaceModels) {
}> { const tagDtos = subspaceModel.tags;
if (tagDtos.length > 0) {
const tagsToAddDto: ProcessTagDto[] = tagDtos
.filter((dto) => dto.action === ModifyAction.ADD)
.map((dto) => ({
name: dto.name,
productUuid: dto.productUuid,
uuid: dto.newTagUuid,
}));
const tagsToDeleteDto = tagDtos.filter(
(dto) => dto.action === ModifyAction.DELETE,
);
if (tagsToAddDto.length > 0) {
let processedTags = await this.tagService.processTags(
tagsToAddDto,
projectUuid,
queryRunner,
);
for (const subspaceDto of subspaceModels) {
if (
subspaceDto !== subspaceModel &&
subspaceDto.action === ModifyAction.UPDATE &&
subspaceDto.tags
) {
// Tag is deleted from one subspace and added in another subspace
const deletedTags = subspaceDto.tags.filter(
(tagDto) =>
tagDto.action === ModifyAction.DELETE &&
processedTags.some((tag) => tag.uuid === tagDto.tagUuid),
);
for (const deletedTag of deletedTags) {
const allocation = await queryRunner.manager.findOne(
SubspaceModelProductAllocationEntity,
{
where: {
subspaceModel: { uuid: subspaceDto.subspaceModel.uuid },
},
relations: ['tags', 'product', 'subspace'],
},
);
const isCommonTag = allocation.tags.some(
(tag) => tag.uuid === deletedTag.tagUuid,
);
if (allocation && isCommonTag) {
const tagEntity = allocation.tags.find(
(tag) => tag.uuid === deletedTag.tagUuid,
);
allocation.tags = allocation.tags.filter(
(tag) => tag.uuid !== deletedTag.tagUuid,
);
await queryRunner.manager.save(allocation);
const productAllocationExistInSubspace =
await queryRunner.manager.findOne(
SubspaceModelProductAllocationEntity,
{
where: {
subspaceModel: {
uuid: subspaceDto.subspaceModel.uuid,
},
product: { uuid: allocation.product.uuid },
},
relations: ['tags'],
},
);
if (productAllocationExistInSubspace) {
productAllocationExistInSubspace.tags.push(tagEntity);
await queryRunner.manager.save(
productAllocationExistInSubspace,
);
} else {
const newProductAllocation = queryRunner.manager.create(
SubspaceModelProductAllocationEntity,
{
subspaceModel: subspaceModel.subspaceModel,
product: allocation.product,
tags: [tagEntity],
},
);
await queryRunner.manager.save(newProductAllocation);
}
// Remove the tag from processedTags to prevent duplication
processedTags = processedTags.filter(
(tag) => tag.uuid !== deletedTag.tagUuid,
);
// Remove the tag from subspaceDto.tags to ensure it's not processed again while processing other dtos.
subspaceDto.tags = subspaceDto.tags.filter(
(tagDto) => tagDto.tagUuid !== deletedTag.tagUuid,
);
}
}
}
if (
subspaceDto !== subspaceModel &&
subspaceDto.action === ModifyAction.DELETE
) {
const allocation = await queryRunner.manager.findOne(
SubspaceModelProductAllocationEntity,
{
where: {
subspaceModel: { uuid: subspaceDto.subspaceModel.uuid },
},
relations: ['tags'],
},
);
const repeatedTags = allocation?.tags.filter((tag) =>
processedTags.some(
(processedTag) => processedTag.uuid === tag.uuid,
),
);
if (repeatedTags.length > 0) {
allocation.tags = allocation.tags.filter(
(tag) =>
!repeatedTags.some(
(repeatedTag) => repeatedTag.uuid === tag.uuid,
),
);
await queryRunner.manager.save(allocation);
const productAllocationExistInSubspace =
await queryRunner.manager.findOne(
SubspaceModelProductAllocationEntity,
{
where: {
subspaceModel: { uuid: subspaceDto.subspaceModel.uuid },
product: { uuid: allocation.product.uuid },
},
relations: ['tags'],
},
);
if (productAllocationExistInSubspace) {
productAllocationExistInSubspace.tags.push(...repeatedTags);
await queryRunner.manager.save(
productAllocationExistInSubspace,
);
} else {
const newProductAllocation = queryRunner.manager.create(
SubspaceModelProductAllocationEntity,
{
subspaceModel: subspaceModel.subspaceModel,
product: allocation.product,
tags: repeatedTags,
},
);
await queryRunner.manager.save(newProductAllocation);
}
const newAllocation = queryRunner.manager.create(
SubspaceModelProductAllocationEntity,
{
subspaceModel: subspaceModel.subspaceModel,
product: allocation.product,
tags: repeatedTags,
},
);
await queryRunner.manager.save(newAllocation);
}
}
}
if (spaceTagUpdateDtos) {
const deletedSpaceTags = spaceTagUpdateDtos.filter(
(tagDto) =>
tagDto.action === ModifyAction.DELETE &&
processedTags.some((tag) => tag.uuid === tagDto.tagUuid),
);
for (const deletedTag of deletedSpaceTags) {
const allocation = await queryRunner.manager.findOne(
SpaceModelProductAllocationEntity,
{
where: {
spaceModel: { uuid: spaceModel.uuid },
tags: { uuid: deletedTag.tagUuid },
},
relations: ['tags', 'subspace'],
},
);
if (
allocation &&
allocation.tags.some((tag) => tag.uuid === deletedTag.tagUuid)
) {
spaceAllocationToExclude.push(allocation);
}
}
}
// Create new product allocations
await this.createProductAllocations(
subspaceModel.subspaceModel,
processedTags,
queryRunner,
spaceAllocationToExclude,
);
}
if (tagsToDeleteDto.length > 0) {
await this.processDeleteActions(tagsToDeleteDto, queryRunner);
}
}
}
}
async clearAllAllocations(subspaceIds: string[], queryRunner: QueryRunner) {
try { try {
const addedSubspaceTags = await this.processAddActions( await queryRunner.manager.delete(SubspaceModelProductAllocationEntity, {
dtos, subspaceModel: In(subspaceIds),
projectUuid, });
subspaceModel,
otherSubspaceModelUpdateDtos,
queryRunner,
spaceModel,
spaceTagUpdateDtos,
);
const deletedTagAllocations = await this.processDeleteActions( await queryRunner.manager
dtos, .createQueryBuilder()
queryRunner, .delete()
); .from('subspace_model_product_tags') // Replace with entity name if you have one
.where(
return { '"subspace_model_product_allocation_uuid" IN (:...subspaceIds)',
addedAllocations: addedSubspaceTags.addedAllocations, {
deletedAllocations: deletedTagAllocations, subspaceIds,
updatedSubspaceDtos: addedSubspaceTags.updatedSubspaceDtos, },
}; )
.execute();
} catch (error) { } catch (error) {
throw this.handleError(error, 'Error while updating product allocations'); throw this.handleError(error, `Failed to clear all allocations`);
} }
} }

View File

@ -9,17 +9,17 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSubspaceModelDto, ModifyTagModelDto } from '../../dtos'; import { CreateSubspaceModelDto, ModifyTagModelDto } from '../../dtos';
import { In, Not, QueryFailedError, QueryRunner } from 'typeorm'; import { In, Not, QueryFailedError, QueryRunner } from 'typeorm';
import { ModifySubspaceModelDto } from 'src/space-model/dtos/subspaces-model-dtos'; import { ModifySubspaceModelDto } from 'src/space-model/dtos/subspaces-model-dtos';
import { TagModelService } from '../tag-model.service';
import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { ProcessTagDto } from 'src/tags/dtos'; import { ProcessTagDto } from 'src/tags/dtos';
import { TagService } from 'src/tags/services'; import { TagService } from 'src/tags/services';
import { SubspaceModelProductAllocationService } from './subspace-model-product-allocation.service'; import { SubspaceModelProductAllocationService } from './subspace-model-product-allocation.service';
import { ISingleSubspaceModel } from 'src/space-model/interfaces';
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
@Injectable() @Injectable()
export class SubSpaceModelService { export class SubSpaceModelService {
constructor( constructor(
private readonly subspaceModelRepository: SubspaceModelRepository, private readonly subspaceModelRepository: SubspaceModelRepository,
private readonly tagModelService: TagModelService,
private readonly tagService: TagService, private readonly tagService: TagService,
private readonly productAllocationService: SubspaceModelProductAllocationService, private readonly productAllocationService: SubspaceModelProductAllocationService,
) {} ) {}
@ -74,36 +74,26 @@ export class SubSpaceModelService {
dtos: ModifySubspaceModelDto[], dtos: ModifySubspaceModelDto[],
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
projectUuid: string, ): Promise<ISingleSubspaceModel[]> {
otherSubspaceModelUpdateDtos?: ModifySubspaceModelDto[], if (!dtos.length) return [];
spaceTagUpdateDtos?: ModifyTagModelDto[],
) {
for (const dto of dtos) {
if (dto.subspaceName) {
await this.checkDuplicateNames(dto.subspaceName, spaceModel.uuid);
const subspaceModel = queryRunner.manager.create(
this.subspaceModelRepository.target,
{
subspaceName: dto.subspaceName,
spaceModel,
},
);
const savedSubspace = await queryRunner.manager.save(subspaceModel);
const subspaceNames = dtos.map((dto) => dto.subspaceName);
if (dto.tags) { await this.checkDuplicateNamesBatch(subspaceNames, spaceModel.uuid);
await this.productAllocationService.updateProductAllocations(
dto.tags, const subspaceEntities = dtos.map((dto) =>
projectUuid, queryRunner.manager.create(SubspaceModelEntity, {
subspaceModel, subspaceName: dto.subspaceName,
otherSubspaceModelUpdateDtos ? otherSubspaceModelUpdateDtos : [], spaceModel,
spaceModel, }),
queryRunner, );
spaceTagUpdateDtos,
); const savedSubspaces = await queryRunner.manager.save(subspaceEntities);
}
} return savedSubspaces.map((subspace, index) => ({
} subspaceModel: subspace,
action: ModifyAction.ADD,
tags: dtos[index].tags ? dtos[index].tags : [],
}));
} }
async modifySubspaceModels( async modifySubspaceModels(
@ -121,19 +111,22 @@ export class SubSpaceModelService {
const combinedDtos = dtos.filter((dto) => dto.action !== ModifyAction.ADD); const combinedDtos = dtos.filter((dto) => dto.action !== ModifyAction.ADD);
const deleteDtos = dtos.filter((dto) => dto.action !== ModifyAction.DELETE); const deleteDtos = dtos.filter((dto) => dto.action !== ModifyAction.DELETE);
await this.updateSubspaceModel( const updatedModels = await this.updateSubspaceModel(
combinedDtos, combinedDtos,
spaceModel, spaceModel,
queryRunner, queryRunner,
projectUuid,
spaceTagUpdateDtos,
); );
await this.handleAddAction( const addedModels = await this.handleAddAction(
addDtos, addDtos,
spaceModel, spaceModel,
queryRunner, queryRunner,
);
const combineModels = [...addedModels, ...updatedModels];
await this.productAllocationService.updateAllocations(
combineModels,
projectUuid, projectUuid,
combinedDtos, queryRunner,
spaceModel,
spaceTagUpdateDtos, spaceTagUpdateDtos,
); );
await this.deleteSubspaceModels(deleteDtos, queryRunner); await this.deleteSubspaceModels(deleteDtos, queryRunner);
@ -155,14 +148,10 @@ export class SubSpaceModelService {
throw new Error('Invalid subspace UUIDs provided.'); throw new Error('Invalid subspace UUIDs provided.');
} }
const subspaces = await queryRunner.manager.find(SubspaceModelEntity, { const subspaces = await this.getSubspacesByUuids(
where: { uuid: In(subspaceUuids) }, queryRunner,
relations: [ subspaceUuids,
'productAllocations', );
'productAllocations.tags',
'spaceModel',
],
});
if (!subspaces.length) return deleteResults; if (!subspaces.length) return deleteResults;
@ -273,57 +262,73 @@ export class SubSpaceModelService {
} }
} }
async clearModels(subspaceUuids: string[], queryRunner: QueryRunner) {
try {
const subspaces = await this.getSubspacesByUuids(
queryRunner,
subspaceUuids,
);
if (!subspaces.length) return;
await queryRunner.manager.update(
SubspaceModelEntity,
{ uuid: In(subspaceUuids) },
{ disabled: true },
);
await this.productAllocationService.clearAllAllocations(
subspaceUuids,
queryRunner,
);
} catch (error) {
throw new HttpException(
error instanceof HttpException
? error.message
: 'An unexpected error occurred while clearing subspace models',
error instanceof HttpException
? error.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async updateSubspaceModel( async updateSubspaceModel(
dtos: ModifySubspaceModelDto[], dtos: ModifySubspaceModelDto[],
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
projectUuid: string, ): Promise<ISingleSubspaceModel[]> {
spaceTagUpdateDtos?: ModifyTagModelDto[], if (!dtos.length) return [];
) {
for (const dto of dtos) {
const otherDtos = dtos.filter((d) => d !== dto);
if (dto.subspaceName) { const updatedSubspaces: {
await this.checkDuplicateNames(dto.subspaceName, spaceModel.uuid); subspaceModel: SubspaceModelEntity;
const subspace = queryRunner.manager.create( tags: ModifyTagModelDto[];
this.subspaceModelRepository.target, action: ModifyAction.UPDATE;
{ }[] = [];
subspaceName: dto.subspaceName,
spaceModel, for (const dto of dtos) {
}, if (!dto.subspaceName) continue;
);
const savedSubspace = await queryRunner.manager.save(subspace); await this.checkDuplicateNames(dto.subspaceName, spaceModel.uuid);
if (dto.tags) {
await this.productAllocationService.updateProductAllocations( const existingSubspace = await queryRunner.manager.findOne(
dto.tags, this.subspaceModelRepository.target,
projectUuid, { where: { uuid: dto.uuid } },
savedSubspace, );
otherDtos,
spaceModel, if (
queryRunner, existingSubspace &&
spaceTagUpdateDtos, existingSubspace.subspaceName !== dto.subspaceName
); ) {
} existingSubspace.subspaceName = dto.subspaceName;
await queryRunner.manager.save(existingSubspace);
updatedSubspaces.push({
subspaceModel: existingSubspace,
tags: dto.tags ?? [],
action: ModifyAction.UPDATE,
});
} }
} }
}
private async findOne(subspaceUuid: string): Promise<SubspaceModelEntity> { return updatedSubspaces;
const subspace = await this.subspaceModelRepository.findOne({
where: { uuid: subspaceUuid, disabled: false },
relations: [
'productAllocations',
'spaceModel',
'productAllocations.tags',
],
});
if (!subspace) {
throw new HttpException(
`SubspaceModel with UUID ${subspaceUuid} not found.`,
HttpStatus.NOT_FOUND,
);
}
return subspace;
} }
private async checkDuplicateNames( private async checkDuplicateNames(
@ -350,6 +355,32 @@ export class SubSpaceModelService {
} }
} }
async checkDuplicateNamesBatch(
subspaceNames: string[],
spaceModelUuid: string,
): Promise<void> {
if (!subspaceNames.length) return;
const existingSubspaces = await this.subspaceModelRepository.find({
where: {
subspaceName: In(subspaceNames),
spaceModel: { uuid: spaceModelUuid },
disabled: false,
},
select: ['subspaceName'],
});
if (existingSubspaces.length > 0) {
const duplicateNames = existingSubspaces.map(
(subspace) => subspace.subspaceName,
);
throw new HttpException(
`Duplicate subspace names found: ${duplicateNames.join(', ')}`,
HttpStatus.CONFLICT,
);
}
}
private async validateNamesInDTO(names: string[]) { private async validateNamesInDTO(names: string[]) {
const seenNames = new Set<string>(); const seenNames = new Set<string>();
const duplicateNames = new Set<string>(); const duplicateNames = new Set<string>();
@ -379,4 +410,18 @@ export class SubSpaceModelService {
): ModifyTagModelDto[] { ): ModifyTagModelDto[] {
return subspaceModels.flatMap((subspace) => subspace.tags || []); return subspaceModels.flatMap((subspace) => subspace.tags || []);
} }
private async getSubspacesByUuids(
queryRunner: QueryRunner,
subspaceUuids: string[],
): Promise<SubspaceModelEntity[]> {
return await queryRunner.manager.find(SubspaceModelEntity, {
where: { uuid: In(subspaceUuids) },
relations: [
'productAllocations',
'productAllocations.tags',
'spaceModel',
],
});
}
} }