mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-14 18:05:48 +00:00
Add space product
This commit is contained in:
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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[];
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
9
src/space/interfaces/single-subspace.interface.ts
Normal file
9
src/space/interfaces/single-subspace.interface.ts
Normal 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[];
|
||||||
|
}
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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 || []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
) || [],
|
) || [],
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user