Add space product

This commit is contained in:
faris Aljohari
2025-02-27 13:36:28 +03:00
parent 902ddbab50
commit a9e5454b61
9 changed files with 619 additions and 45 deletions

View File

@ -1,6 +1,7 @@
import { DataSource, Repository } from 'typeorm'; import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { SubspaceEntity } from '../entities/subspace/subspace.entity'; import { SubspaceEntity } from '../entities/subspace/subspace.entity';
import { SubspaceProductAllocationEntity } from '../entities/subspace/subspace-product-allocation.entity';
@Injectable() @Injectable()
export class SubspaceRepository extends Repository<SubspaceEntity> { export class SubspaceRepository extends Repository<SubspaceEntity> {
@ -8,3 +9,9 @@ export class SubspaceRepository extends Repository<SubspaceEntity> {
super(SubspaceEntity, dataSource.createEntityManager()); super(SubspaceEntity, dataSource.createEntityManager());
} }
} }
@Injectable()
export class SubspaceProductAllocationRepository extends Repository<SubspaceProductAllocationEntity> {
constructor(private dataSource: DataSource) {
super(SubspaceProductAllocationEntity, dataSource.createEntityManager());
}
}

View File

@ -14,7 +14,6 @@ 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 { 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 {

View File

@ -6,8 +6,8 @@ import {
IsString, IsString,
ValidateNested, ValidateNested,
} from 'class-validator'; } from 'class-validator';
import { CreateTagDto } from '../tag';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { ProcessTagDto } from 'src/tags/dtos';
export class AddSubspaceDto { export class AddSubspaceDto {
@ApiProperty({ @ApiProperty({
@ -20,11 +20,11 @@ export class AddSubspaceDto {
@ApiProperty({ @ApiProperty({
description: 'List of tags associated with the subspace', description: 'List of tags associated with the subspace',
type: [CreateTagDto], type: [ProcessTagDto],
}) })
@IsArray() @IsArray()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@Type(() => CreateTagDto) @Type(() => ProcessTagDto)
@IsOptional() @IsOptional()
tags?: CreateTagDto[]; tags?: ProcessTagDto[];
} }

View File

@ -1,6 +1,6 @@
import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsOptional, IsString } from 'class-validator'; import { IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';
export class ModifyTagDto { export class ModifyTagDto {
@ApiProperty({ @ApiProperty({
@ -11,12 +11,21 @@ export class ModifyTagDto {
action: ModifyAction; action: ModifyAction;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'UUID of the tag (required for update/delete)', description: 'UUID of the new tag',
example: '123e4567-e89b-12d3-a456-426614174000', example: '123e4567-e89b-12d3-a456-426614174000',
}) })
@IsOptional() @IsOptional()
@IsString() @IsUUID()
uuid?: string; newTagUuid: string;
@ApiPropertyOptional({
description:
'UUID of an existing tag (required for update/delete, optional for add)',
example: 'a1b2c3d4-5678-90ef-abcd-1234567890ef',
})
@IsOptional()
@IsUUID()
tagUuid?: string;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Name of the tag (required for add/update)', description: 'Name of the tag (required for add/update)',
@ -24,7 +33,7 @@ export class ModifyTagDto {
}) })
@IsOptional() @IsOptional()
@IsString() @IsString()
tag?: string; name?: string;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: description:
@ -32,6 +41,6 @@ export class ModifyTagDto {
example: 'c789a91e-549a-4753-9006-02f89e8170e0', example: 'c789a91e-549a-4753-9006-02f89e8170e0',
}) })
@IsOptional() @IsOptional()
@IsString() @IsUUID()
productUuid?: string; productUuid?: string;
} }

View File

@ -0,0 +1,9 @@
import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
import { ModifyTagDto } from '../dtos/tag/modify-tag.dto';
export interface ISingleSubspace {
subspace: SubspaceEntity;
action: ModifyAction;
tags: ModifyTagDto[];
}

View File

@ -0,0 +1,508 @@
import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { ProductEntity } from '@app/common/modules/product/entities';
import { SpaceProductAllocationRepository } from '@app/common/modules/space';
import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities/space-product-allocation.entity';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
import { SubspaceProductAllocationEntity } from '@app/common/modules/space/entities/subspace/subspace-product-allocation.entity';
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
import { SubspaceProductAllocationRepository } from '@app/common/modules/space/repositories/subspace.repository';
import { NewTagEntity } from '@app/common/modules/tag';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
import { ISingleSubspace } from 'src/space/interfaces/single-subspace.interface';
import { ProcessTagDto } from 'src/tags/dtos';
import { TagService as NewTagService } from 'src/tags/services';
import { In, QueryRunner } from 'typeorm';
@Injectable()
export class SubspaceProductAllocationService {
constructor(
private readonly tagService: NewTagService,
private readonly spaceProductAllocationRepository: SpaceProductAllocationRepository,
private readonly subspaceProductAllocationRepository: SubspaceProductAllocationRepository,
) {}
async createSubspaceProductAllocations(
subspace: SubspaceEntity,
processedTags: NewTagEntity[],
queryRunner?: QueryRunner,
spaceAllocationsToExclude?: SpaceProductAllocationEntity[],
): Promise<void> {
try {
if (!processedTags.length) return;
const allocations: SubspaceProductAllocationEntity[] = [];
for (const tag of processedTags) {
await this.validateTagWithinSubspace(
queryRunner,
tag,
subspace,
spaceAllocationsToExclude,
);
let allocation = await this.getAllocationByProduct(
tag.product,
subspace,
queryRunner,
);
if (!allocation) {
allocation = this.createNewSubspaceAllocation(
subspace,
tag,
queryRunner,
);
allocations.push(allocation);
} else if (!allocation.tags.some((t) => t.uuid === tag.uuid)) {
allocation.tags.push(tag);
await this.saveAllocation(allocation, queryRunner);
}
}
if (allocations.length > 0) {
await this.saveAllocations(allocations, queryRunner);
}
} catch (error) {
throw this.handleError(
error,
'Failed to create subspace product allocations',
);
}
}
async updateSubspaceProductAllocations(
subspaces: ISingleSubspace[],
projectUuid: string,
queryRunner: QueryRunner,
space: SpaceEntity,
spaceTagUpdateDtos?: ModifyTagDto[],
) {
const spaceAllocationToExclude: SpaceProductAllocationEntity[] = [];
for (const subspace of subspaces) {
const tagDtos = subspace.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 subspaces) {
if (
subspaceDto !== subspace &&
subspaceDto.action === ModifyAction.UPDATE &&
subspaceDto.tags
) {
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(
SubspaceProductAllocationEntity,
{
where: {
subspace: { uuid: subspaceDto.subspace.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(
SubspaceProductAllocationEntity,
{
where: {
subspace: {
uuid: subspaceDto.subspace.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(
SubspaceProductAllocationEntity,
{
subspace: subspace.subspace,
product: allocation.product,
tags: [tagEntity],
},
);
await queryRunner.manager.save(newProductAllocation);
}
processedTags = processedTags.filter(
(tag) => tag.uuid !== deletedTag.tagUuid,
);
subspaceDto.tags = subspaceDto.tags.filter(
(tagDto) => tagDto.tagUuid !== deletedTag.tagUuid,
);
}
}
}
if (
subspaceDto !== subspace &&
subspaceDto.action === ModifyAction.DELETE
) {
const allocation = await queryRunner.manager.findOne(
SubspaceProductAllocationEntity,
{
where: {
subspace: { uuid: subspaceDto.subspace.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(
SubspaceProductAllocationEntity,
{
where: {
subspace: { uuid: subspaceDto.subspace.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(
SubspaceProductAllocationEntity,
{
subspace: subspace.subspace,
product: allocation.product,
tags: repeatedTags,
},
);
await queryRunner.manager.save(newProductAllocation);
}
const newAllocation = queryRunner.manager.create(
SubspaceProductAllocationEntity,
{
subspace: subspace.subspace,
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(
SpaceProductAllocationEntity,
{
where: {
space: { uuid: space.uuid },
tags: { uuid: deletedTag.tagUuid },
},
relations: ['tags', 'subspace'],
},
);
if (
allocation &&
allocation.tags.some((tag) => tag.uuid === deletedTag.tagUuid)
) {
spaceAllocationToExclude.push(allocation);
}
}
}
await this.createSubspaceProductAllocations(
subspace.subspace,
processedTags,
queryRunner,
spaceAllocationToExclude,
);
}
if (tagsToDeleteDto.length > 0) {
await this.processDeleteActions(tagsToDeleteDto, queryRunner);
}
}
}
}
async processDeleteActions(
dtos: ModifyTagDto[],
queryRunner: QueryRunner,
): Promise<SubspaceProductAllocationEntity[]> {
try {
if (!dtos || dtos.length === 0) {
throw new Error('No DTOs provided for deletion.');
}
const tagUuidsToDelete = dtos
.filter((dto) => dto.action === ModifyAction.DELETE && dto.tagUuid)
.map((dto) => dto.tagUuid);
if (tagUuidsToDelete.length === 0) return [];
const allocationsToUpdate = await queryRunner.manager.find(
SubspaceProductAllocationEntity,
{
where: { tags: { uuid: In(tagUuidsToDelete) } },
relations: ['tags'],
},
);
if (!allocationsToUpdate || allocationsToUpdate.length === 0) return [];
const deletedAllocations: SubspaceProductAllocationEntity[] = [];
const allocationUpdates: SubspaceProductAllocationEntity[] = [];
for (const allocation of allocationsToUpdate) {
const updatedTags = allocation.tags.filter(
(tag) => !tagUuidsToDelete.includes(tag.uuid),
);
if (updatedTags.length === allocation.tags.length) {
continue;
}
if (updatedTags.length === 0) {
deletedAllocations.push(allocation);
} else {
allocation.tags = updatedTags;
allocationUpdates.push(allocation);
}
}
if (allocationUpdates.length > 0) {
await queryRunner.manager.save(
SubspaceProductAllocationEntity,
allocationUpdates,
);
}
if (deletedAllocations.length > 0) {
await queryRunner.manager.remove(
SubspaceProductAllocationEntity,
deletedAllocations,
);
}
await queryRunner.manager
.createQueryBuilder()
.delete()
.from('subspace_product_tags')
.where(
'subspace_product_allocation_id NOT IN ' +
queryRunner.manager
.createQueryBuilder()
.select('uuid')
.from(SubspaceProductAllocationEntity, 'allocation')
.getQuery(),
)
.execute();
return deletedAllocations;
} catch (error) {
throw this.handleError(error, `Failed to delete tags in subspace`);
}
}
private async validateTagWithinSubspace(
queryRunner: QueryRunner | undefined,
tag: NewTagEntity,
subspace: SubspaceEntity,
spaceAllocationsToExclude?: SpaceProductAllocationEntity[],
): Promise<void> {
const existingTagInSpace = await (queryRunner
? queryRunner.manager.findOne(SpaceProductAllocationEntity, {
where: {
product: tag.product,
space: subspace.space,
tags: { uuid: tag.uuid },
},
relations: ['tags'],
})
: this.spaceProductAllocationRepository.findOne({
where: {
product: tag.product,
space: subspace.space,
tags: { uuid: tag.uuid },
},
relations: ['tags'],
}));
const isExcluded = spaceAllocationsToExclude?.some(
(excludedAllocation) =>
excludedAllocation.product.uuid === tag.product.uuid &&
excludedAllocation.tags.some((t) => t.uuid === tag.uuid),
);
if (!isExcluded && existingTagInSpace) {
throw new HttpException(
`Tag ${tag.uuid} (Product: ${tag.product.uuid}) is already allocated at the space level (${subspace.space.uuid}). Cannot allocate the same tag in a subspace.`,
HttpStatus.BAD_REQUEST,
);
}
const existingTagInSameSpace = await (queryRunner
? queryRunner.manager.findOne(SubspaceProductAllocationEntity, {
where: {
product: tag.product,
subspace: { space: subspace.space },
tags: { uuid: tag.uuid },
},
relations: ['subspace', 'tags'],
})
: this.subspaceProductAllocationRepository.findOne({
where: {
product: tag.product,
subspace: { space: subspace.space },
tags: { uuid: tag.uuid },
},
relations: ['subspace', 'tags'],
}));
if (
existingTagInSameSpace &&
existingTagInSameSpace.subspace.uuid !== subspace.uuid
) {
throw new HttpException(
`Tag ${tag.uuid} (Product: ${tag.product.uuid}) is already allocated in another subspace (${existingTagInSameSpace.subspace.uuid}) within the same space (${subspace.space.uuid}).`,
HttpStatus.BAD_REQUEST,
);
}
}
private createNewSubspaceAllocation(
subspace: SubspaceEntity,
tag: NewTagEntity,
queryRunner?: QueryRunner,
): SubspaceProductAllocationEntity {
return queryRunner
? queryRunner.manager.create(SubspaceProductAllocationEntity, {
subspace,
product: tag.product,
tags: [tag],
})
: this.subspaceProductAllocationRepository.create({
subspace,
product: tag.product,
tags: [tag],
});
}
private async getAllocationByProduct(
product: ProductEntity,
subspace: SubspaceEntity,
queryRunner?: QueryRunner,
): Promise<SubspaceProductAllocationEntity | null> {
return queryRunner
? queryRunner.manager.findOne(SubspaceProductAllocationEntity, {
where: { subspace, product },
relations: ['tags'],
})
: this.subspaceProductAllocationRepository.findOne({
where: { subspace, product },
relations: ['tags'],
});
}
private async saveAllocation(
allocation: SubspaceProductAllocationEntity,
queryRunner?: QueryRunner,
): Promise<void> {
if (queryRunner) {
await queryRunner.manager.save(
SubspaceProductAllocationEntity,
allocation,
);
} else {
await this.subspaceProductAllocationRepository.save(allocation);
}
}
private async saveAllocations(
allocations: SubspaceProductAllocationEntity[],
queryRunner?: QueryRunner,
): Promise<void> {
if (queryRunner) {
await queryRunner.manager.save(
SubspaceProductAllocationEntity,
allocations,
);
} else {
await this.subspaceProductAllocationRepository.save(allocations);
}
}
private handleError(error: any, message: string): HttpException {
return new HttpException(
error instanceof HttpException ? error.message : message,
error instanceof HttpException
? error.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}

View File

@ -2,7 +2,6 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { import {
AddSubspaceDto, AddSubspaceDto,
CreateTagDto,
DeleteSubspaceDto, DeleteSubspaceDto,
GetSpaceParam, GetSpaceParam,
GetSubSpaceParam, GetSubSpaceParam,
@ -26,6 +25,9 @@ import { SubspaceDeviceService } from './subspace-device.service';
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto'; import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity'; import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
import { ProcessTagDto } from 'src/tags/dtos';
import { TagService as NewTagService } from 'src/tags/services/tags.service';
import { SubspaceProductAllocationService } from './subspace-product-allocation.service';
@Injectable() @Injectable()
export class SubSpaceService { export class SubSpaceService {
@ -33,7 +35,9 @@ export class SubSpaceService {
private readonly subspaceRepository: SubspaceRepository, private readonly subspaceRepository: SubspaceRepository,
private readonly validationService: ValidationService, private readonly validationService: ValidationService,
private readonly tagService: TagService, private readonly tagService: TagService,
private readonly newTagService: NewTagService,
public readonly deviceService: SubspaceDeviceService, public readonly deviceService: SubspaceDeviceService,
private readonly subspaceProductAllocationService: SubspaceProductAllocationService,
) {} ) {}
async createSubspaces( async createSubspaces(
@ -51,6 +55,7 @@ export class SubSpaceService {
subspaceNames, subspaceNames,
subspaceData[0].space, subspaceData[0].space,
); );
const subspaces = subspaceData.map((data) => const subspaces = subspaceData.map((data) =>
queryRunner.manager.create(this.subspaceRepository.target, data), queryRunner.manager.create(this.subspaceRepository.target, data),
); );
@ -94,7 +99,8 @@ export class SubSpaceService {
addSubspaceDtos: AddSubspaceDto[], addSubspaceDtos: AddSubspaceDto[],
space: SpaceEntity, space: SpaceEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
otherTags?: CreateTagDto[], otherTags?: ProcessTagDto[],
projectUuid?: string,
): Promise<SubspaceEntity[]> { ): Promise<SubspaceEntity[]> {
try { try {
this.checkForDuplicateNames( this.checkForDuplicateNames(
@ -107,20 +113,22 @@ export class SubSpaceService {
})); }));
const subspaces = await this.createSubspaces(subspaceData, queryRunner); const subspaces = await this.createSubspaces(subspaceData, queryRunner);
await Promise.all( await Promise.all(
addSubspaceDtos.map(async (dto, index) => { addSubspaceDtos.map(async (dto, index) => {
const otherDtoTags = addSubspaceDtos
.filter((_, i) => i !== index)
.flatMap((otherDto) => otherDto.tags || []);
const subspace = subspaces[index]; const subspace = subspaces[index];
if (dto.tags?.length) {
subspace.tags = await this.tagService.createTags( const allTags = [...(dto.tags || []), ...(otherTags || [])];
dto.tags,
if (allTags.length) {
const processedTags = await this.newTagService.processTags(
allTags,
projectUuid,
queryRunner, queryRunner,
null, );
await this.subspaceProductAllocationService.createSubspaceProductAllocations(
subspace, subspace,
[...(otherTags || []), ...otherDtoTags], processedTags,
queryRunner,
); );
} }
}), }),
@ -318,14 +326,28 @@ export class SubSpaceService {
subspaceDtos: ModifySubspaceDto[], subspaceDtos: ModifySubspaceDto[],
queryRunner: QueryRunner, queryRunner: QueryRunner,
space?: SpaceEntity, space?: SpaceEntity,
projectUuid?: string,
spaceTagUpdateDtos?: ModifyTagDto[],
) { ) {
const addedSubspaces = [];
const updatedSubspaces = [];
for (const subspace of subspaceDtos) { for (const subspace of subspaceDtos) {
switch (subspace.action) { switch (subspace.action) {
case ModifyAction.ADD: case ModifyAction.ADD:
await this.handleAddAction(subspace, space, queryRunner); const addedSubspace = await this.handleAddAction(
subspace,
space,
queryRunner,
);
addedSubspaces.push(addedSubspace);
break; break;
case ModifyAction.UPDATE: case ModifyAction.UPDATE:
await this.handleUpdateAction(subspace, queryRunner); const updatedSubspace = await this.handleUpdateAction(
subspace,
queryRunner,
);
updatedSubspaces.push(updatedSubspace);
break; break;
case ModifyAction.DELETE: case ModifyAction.DELETE:
await this.handleDeleteAction(subspace, queryRunner); await this.handleDeleteAction(subspace, queryRunner);
@ -337,6 +359,18 @@ export class SubSpaceService {
); );
} }
} }
const combinedSubspaces = [...addedSubspaces, ...updatedSubspaces];
if (combinedSubspaces.length > 0) {
await this.subspaceProductAllocationService.updateSubspaceProductAllocations(
combinedSubspaces,
projectUuid,
queryRunner,
space,
spaceTagUpdateDtos,
);
}
} }
async unlinkModels( async unlinkModels(
@ -382,10 +416,10 @@ export class SubSpaceService {
space: SpaceEntity, space: SpaceEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<SubspaceEntity> { ): Promise<SubspaceEntity> {
const createTagDtos: CreateTagDto[] = const createTagDtos: ProcessTagDto[] =
subspace.tags?.map((tag) => ({ subspace.tags?.map((tag) => ({
tag: tag.tag as string, tag: tag.name as string,
uuid: tag.uuid, uuid: tag.tagUuid,
productUuid: tag.productUuid as string, productUuid: tag.productUuid as string,
})) || []; })) || [];
const subSpace = await this.createSubspacesFromDto( const subSpace = await this.createSubspacesFromDto(
@ -440,7 +474,7 @@ export class SubSpaceService {
); );
if (subspace.tags?.length) { if (subspace.tags?.length) {
const modifyTagDtos: CreateTagDto[] = subspace.tags.map((tag) => ({ const modifyTagDtos: ProcessTagDto[] = subspace.tags.map((tag) => ({
uuid: tag.uuid, uuid: tag.uuid,
action: ModifyAction.ADD, action: ModifyAction.ADD,
tag: tag.tag, tag: tag.tag,
@ -538,4 +572,7 @@ export class SubSpaceService {
await this.checkForDuplicateNames(names); await this.checkForDuplicateNames(names);
await this.checkExistingNamesInSpace(names, space); await this.checkExistingNamesInSpace(names, space);
} }
extractTagsFromSubspace(addSubspaceDto: AddSubspaceDto[]): ProcessTagDto[] {
return addSubspaceDto.flatMap((subspace) => subspace.tags || []);
}
} }

View File

@ -177,20 +177,20 @@ export class TagService {
subspace?: SubspaceEntity, subspace?: SubspaceEntity,
): Promise<TagEntity> { ): Promise<TagEntity> {
try { try {
const existingTag = await this.getTagByUuid(tag.uuid); const existingTag = await this.getTagByUuid(tag.tagUuid);
const contextSpace = space ?? subspace?.space; const contextSpace = space ?? subspace?.space;
if (contextSpace && tag.tag !== existingTag.tag) { if (contextSpace && tag.name !== existingTag.tag) {
await this.checkTagReuse( await this.checkTagReuse(
tag.tag, tag.name,
existingTag.product.uuid, existingTag.product.uuid,
contextSpace, contextSpace,
); );
} }
return await queryRunner.manager.save( return await queryRunner.manager.save(
Object.assign(existingTag, { tag: tag.tag }), Object.assign(existingTag, { tag: tag.name }),
); );
} catch (error) { } catch (error) {
throw this.handleUnexpectedError('Failed to update tags', error); throw this.handleUnexpectedError('Failed to update tags', error);
@ -297,9 +297,9 @@ export class TagService {
await this.createTags( await this.createTags(
[ [
{ {
tag: tag.tag, tag: tag.name,
productUuid: tag.productUuid, productUuid: tag.productUuid,
uuid: tag.uuid, uuid: tag.tagUuid,
}, },
], ],
queryRunner, queryRunner,
@ -313,7 +313,7 @@ export class TagService {
await this.updateTag(tag, queryRunner, space, subspace); await this.updateTag(tag, queryRunner, space, subspace);
break; break;
case ModifyAction.DELETE: case ModifyAction.DELETE:
await this.deleteTags([tag.uuid], queryRunner); await this.deleteTags([tag.tagUuid], queryRunner);
break; break;
default: default:
throw new HttpException( throw new HttpException(
@ -385,7 +385,9 @@ export class TagService {
const filteredTagExists = tagExists.filter( const filteredTagExists = tagExists.filter(
(existingTag) => (existingTag) =>
!tagsToDelete?.some((deleteTag) => deleteTag.uuid === existingTag.uuid), !tagsToDelete?.some(
(deleteTag) => deleteTag.tagUuid === existingTag.uuid,
),
); );
if (filteredTagExists.length > 0) { if (filteredTagExists.length > 0) {
@ -517,14 +519,14 @@ export class TagService {
tagsToAdd tagsToAdd
.filter((tagToAdd) => .filter((tagToAdd) =>
spaceTagsToDelete.some( spaceTagsToDelete.some(
(tagToDelete) => tagToAdd.uuid === tagToDelete.uuid, (tagToDelete) => tagToAdd.tagUuid === tagToDelete.tagUuid,
), ),
) )
.map((tag) => tag.uuid), .map((tag) => tag.tagUuid),
); );
const remainingTags = spaceTags.filter( const remainingTags = spaceTags.filter(
(tag) => !commonTagUuids.has(tag.uuid), // Exclude tags in commonTagUuids (tag) => !commonTagUuids.has(tag.tagUuid), // Exclude tags in commonTagUuids
); );
return remainingTags; return remainingTags;
@ -551,11 +553,11 @@ export class TagService {
); );
const subspaceTagsToDeleteUuids = new Set( const subspaceTagsToDeleteUuids = new Set(
subspaceTagsToDelete.map((tag) => tag.uuid), subspaceTagsToDelete.map((tag) => tag.tagUuid),
); );
const commonTagsInSubspaces = subspaceTagsToAdd.filter((tag) => const commonTagsInSubspaces = subspaceTagsToAdd.filter((tag) =>
subspaceTagsToDeleteUuids.has(tag.uuid), subspaceTagsToDeleteUuids.has(tag.tagUuid),
); );
// Find UUIDs of tags that are common between spaceTagsToAdd and subspace tags marked for deletion // Find UUIDs of tags that are common between spaceTagsToAdd and subspace tags marked for deletion
@ -567,17 +569,18 @@ export class TagService {
subspace.tags?.filter( subspace.tags?.filter(
(tagToDelete) => (tagToDelete) =>
tagToDelete.action === 'delete' && tagToDelete.action === 'delete' &&
tagToAdd.uuid === tagToDelete.uuid, tagToAdd.tagUuid === tagToDelete.tagUuid,
) || [], ) || [],
), ),
) )
.map((tag) => tag.uuid), .map((tag) => tag.tagUuid),
); );
// Modify subspaceModels by removing tags with UUIDs present in commonTagUuids // Modify subspaceModels by removing tags with UUIDs present in commonTagUuids
let modifiedSubspaces = subspaceModels.map((subspace) => ({ let modifiedSubspaces = subspaceModels.map((subspace) => ({
...subspace, ...subspace,
tags: subspace.tags?.filter((tag) => !commonTagUuids.has(tag.uuid)) || [], tags:
subspace.tags?.filter((tag) => !commonTagUuids.has(tag.tagUuid)) || [],
})); }));
modifiedSubspaces = modifiedSubspaces.map((subspace) => ({ modifiedSubspaces = modifiedSubspaces.map((subspace) => ({
@ -588,7 +591,7 @@ export class TagService {
!( !(
tag.action === 'delete' && tag.action === 'delete' &&
commonTagsInSubspaces.some( commonTagsInSubspaces.some(
(commonTag) => commonTag.uuid === tag.uuid, (commonTag) => commonTag.tagUuid === tag.tagUuid,
) )
), ),
) || [], ) || [],

View File

@ -171,6 +171,8 @@ export class TagService {
return newTags; return newTags;
} catch (error) { } catch (error) {
console.log('error1', error);
throw new HttpException( throw new HttpException(
error instanceof HttpException error instanceof HttpException
? error.message ? error.message