diff --git a/src/space-model/commands/index.ts b/src/space-model/commands/index.ts index 4a7aafa..59f7dfc 100644 --- a/src/space-model/commands/index.ts +++ b/src/space-model/commands/index.ts @@ -1,2 +1,3 @@ export * from './propagate-space-model-deletion.command'; export * from './propagate-subspace-model-update-command'; +export * from './propagate-space-model-product-allocation-command'; \ No newline at end of file diff --git a/src/space-model/commands/propagate-space-model-product-allocation-command.ts b/src/space-model/commands/propagate-space-model-product-allocation-command.ts new file mode 100644 index 0000000..1240bd3 --- /dev/null +++ b/src/space-model/commands/propagate-space-model-product-allocation-command.ts @@ -0,0 +1,16 @@ +import { ICommand } from '@nestjs/cqrs'; +import { SpaceModelEntity } from '@app/common/modules/space-model'; +import { IUpdatedSpaceAllocations } from '../interfaces'; +import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; + +export class PropogateUpdateSpaceModelProductAllocationCommand + implements ICommand +{ + constructor( + public readonly param: { + spaceModel: SpaceModelEntity; + updatedAllocations: IUpdatedSpaceAllocations[]; + spaces: SpaceEntity[]; + }, + ) {} +} diff --git a/src/space-model/commands/propagate-subspace-model-update-command.ts b/src/space-model/commands/propagate-subspace-model-update-command.ts index ff437e4..28b9a9a 100644 --- a/src/space-model/commands/propagate-subspace-model-update-command.ts +++ b/src/space-model/commands/propagate-subspace-model-update-command.ts @@ -1,13 +1,13 @@ import { ICommand } from '@nestjs/cqrs'; import { SpaceModelEntity } from '@app/common/modules/space-model'; -import { ISingleSubspaceModel } from '../interfaces'; +import { ISubspaceModelUpdates } from '../interfaces'; import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; export class PropogateUpdateSpaceModelCommand implements ICommand { constructor( public readonly param: { spaceModel: SpaceModelEntity; - subspaceModels: ISingleSubspaceModel[]; + subspaceUpdates: ISubspaceModelUpdates; spaces: SpaceEntity[]; }, ) {} diff --git a/src/space-model/handlers/index.ts b/src/space-model/handlers/index.ts index d7bb550..ff9ffbb 100644 --- a/src/space-model/handlers/index.ts +++ b/src/space-model/handlers/index.ts @@ -1,2 +1,3 @@ -export * from './propate-subspace-handler'; +export * from './propogate-subspace-handler'; export * from './propogate-space-model-deletion.handler'; +export * from './propogate-space-model-product-allocation.handler'; diff --git a/src/space-model/handlers/propate-subspace-handler.ts b/src/space-model/handlers/propate-subspace-handler.ts deleted file mode 100644 index c44f621..0000000 --- a/src/space-model/handlers/propate-subspace-handler.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { PropogateUpdateSpaceModelCommand } from '../commands'; -import { - SpaceProductAllocationRepository, - SpaceRepository, -} from '@app/common/modules/space'; -import { - SubspaceProductAllocationRepository, - SubspaceRepository, -} from '@app/common/modules/space/repositories/subspace.repository'; -import { - SpaceModelEntity, - SubspaceModelProductAllocationRepoitory, - TagModel, -} from '@app/common/modules/space-model'; -import { In, QueryRunner } from 'typeorm'; -import { TagService } from 'src/space/services/tag'; -import { - ISingleSubspaceModel, - UpdatedSubspaceModelPayload, -} from '../interfaces'; -import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; -import { ModifyAction } from '@app/common/constants/modify-action.enum'; - -@CommandHandler(PropogateUpdateSpaceModelCommand) -export class PropogateUpdateSpaceModelHandler - implements ICommandHandler -{ - constructor( - private readonly spaceRepository: SpaceRepository, - private readonly subspaceRepository: SubspaceRepository, - private readonly tagService: TagService, - private readonly subspaceModelProductRepository: SubspaceModelProductAllocationRepoitory, - private readonly subspaceProductRepository: SubspaceProductAllocationRepository, - private readonly spaceProductRepository: SpaceProductAllocationRepository, - ) {} - - async execute(command: PropogateUpdateSpaceModelCommand): Promise { - const { subspaceModels, spaces } = command.param; - try { - if (!subspaceModels || subspaceModels.length === 0) return; - if (!spaces || spaces.length === 0) return; - - for (const subspaceModel of subspaceModels) { - if (subspaceModel.action === ModifyAction.ADD) { - await this.addSubspaceModel(subspaceModel, spaces); - } else if (subspaceModel.action === ModifyAction.DELETE) { - await this.deleteSubspaceModel(subspaceModel, spaces); - } else if (subspaceModel.action === ModifyAction.UPDATE) { - await this.updateSubspaceModel(subspaceModel); - } - } - } catch (error) { - console.error(`Error processing subspaceModel updates`, error); - } - } - - async addSubspaceModel( - subspaceModel: ISingleSubspaceModel, - spaces: SpaceEntity[], - ) { - const subspaceModelAllocations = - await this.subspaceModelProductRepository.find({ - where: { - subspaceModel: { - uuid: subspaceModel.subspaceModel.uuid, - }, - }, - relations: ['tags', 'product'], - }); - - for (const space of spaces) { - const subspace = this.subspaceRepository.create({ - subspaceName: subspaceModel.subspaceModel.subspaceName, - space: space, - }); - - subspace.subSpaceModel = subspaceModel.subspaceModel; - await this.subspaceRepository.save(subspace); - - if (subspaceModelAllocations?.length > 0) { - for (const allocation of subspaceModelAllocations) { - const subspaceAllocation = this.subspaceProductRepository.create({ - subspace: subspace, - product: allocation.product, - tags: allocation.tags, - inheritedFromModel: allocation, - }); - await this.subspaceProductRepository.save(subspaceAllocation); - } - } - } - } - - async deleteSubspaceModel( - subspaceModel: ISingleSubspaceModel, - spaces: SpaceEntity[], - ) { - const subspaces = await this.subspaceRepository.find({ - where: { - subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid }, - disabled: false, - }, - relations: [ - 'productAllocations', - 'productAllocations.product', - 'productAllocations.tags', - ], - }); - - if (!subspaces.length) { - return; - } - - const allocationUuidsToRemove = subspaces.flatMap((subspace) => - subspace.productAllocations.map((allocation) => allocation.uuid), - ); - - if (allocationUuidsToRemove.length) { - await this.subspaceProductRepository.delete(allocationUuidsToRemove); - } - - await this.subspaceRepository.update( - { uuid: In(subspaces.map((s) => s.uuid)) }, - { disabled: true }, - ); - - const relocatedAllocations = subspaceModel.relocatedAllocations || []; - - if (!relocatedAllocations.length) { - return; - } - - for (const space of spaces) { - for (const { allocation, tags = [] } of relocatedAllocations) { - const spaceAllocation = await this.spaceProductRepository.findOne({ - where: { - inheritedFromModel: { uuid: allocation.uuid }, - space: { uuid: space.uuid }, - }, - relations: ['tags'], - }); - - if (spaceAllocation) { - if (tags.length) { - spaceAllocation.tags.push(...tags); - await this.spaceProductRepository.save(spaceAllocation); - } - } else { - const newSpaceAllocation = this.spaceProductRepository.create({ - space, - inheritedFromModel: allocation, - tags: allocation.tags, - product: allocation.product, - }); - await this.spaceProductRepository.save(newSpaceAllocation); - } - } - } - } - - async updateSubspaceModel(subspaceModel: ISingleSubspaceModel) { - return this.subspaceRepository.update( - { - subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid }, - disabled: false, - }, - { subspaceName: subspaceModel.subspaceModel.subspaceName }, - ); - } - async updateSubspaceModels( - subspaceModels: UpdatedSubspaceModelPayload[], - queryRunner: QueryRunner, - ): Promise { - const subspaceUpdatePromises = subspaceModels.map(async (model) => { - const { - updated: tagsToUpdate, - deleted: tagsToDelete, - added: tagsToAdd, - } = model.modifiedTags; - - // Perform tag operations concurrently - await Promise.all([ - tagsToUpdate?.length && this.updateTags(tagsToUpdate, queryRunner), - tagsToDelete?.length && this.deleteTags(tagsToDelete, queryRunner), - tagsToAdd?.length && - this.createTags( - tagsToAdd, - queryRunner, - model.subspaceModelUuid, - null, - ), - ]); - - // Update subspace names - const subspaces = await queryRunner.manager.find( - this.subspaceRepository.target, - { - where: { - subSpaceModel: { - uuid: model.subspaceModelUuid, - }, - }, - }, - ); - - if (subspaces.length > 0) { - const updateSubspacePromises = subspaces.map((subspace) => - queryRunner.manager.update( - this.subspaceRepository.target, - { uuid: subspace.uuid }, - { subspaceName: model.subspaceName }, - ), - ); - await Promise.all(updateSubspacePromises); - } - }); - - // Wait for all subspace model updates to complete - await Promise.all(subspaceUpdatePromises); - } - async updateTags(models: TagModel[], queryRunner: QueryRunner) { - if (!models?.length) return; - - const updatePromises = models.map((model) => - this.tagService.updateTagsFromModel(model, queryRunner), - ); - await Promise.all(updatePromises); - } - - async deleteTags(uuids: string[], queryRunner: QueryRunner) { - const deletePromises = uuids.map((uuid) => - this.tagService.deleteTagFromModel(uuid, queryRunner), - ); - await Promise.all(deletePromises); - } - - async createTags( - models: TagModel[], - queryRunner: QueryRunner, - subspaceModelUuid?: string, - spaceModel?: SpaceModelEntity, - ): Promise { - if (!models.length) { - return; - } - - if (subspaceModelUuid) { - await this.processSubspaces(subspaceModelUuid, models, queryRunner); - } - - if (spaceModel) { - await this.processSpaces(spaceModel.uuid, models, queryRunner); - } - } - - private async processSubspaces( - subspaceModelUuid: string, - models: TagModel[], - queryRunner: QueryRunner, - ): Promise { - const subspaces = await this.subspaceRepository.find({ - where: { - subSpaceModel: { - uuid: subspaceModelUuid, - }, - }, - }); - - if (subspaces.length > 0) { - const subspacePromises = subspaces.map((subspace) => - this.tagService.createTagsFromModel( - queryRunner, - models, - null, - subspace, - ), - ); - await Promise.all(subspacePromises); - } - } - - private async processSpaces( - spaceModelUuid: string, - models: TagModel[], - queryRunner: QueryRunner, - ): Promise { - const spaces = await this.spaceRepository.find({ - where: { - spaceModel: { - uuid: spaceModelUuid, - }, - }, - }); - - if (spaces.length > 0) { - const spacePromises = spaces.map((space) => - this.tagService.createTagsFromModel(queryRunner, models, space, null), - ); - await Promise.all(spacePromises); - } - } -} diff --git a/src/space-model/handlers/propogate-space-model-product-allocation.handler.ts b/src/space-model/handlers/propogate-space-model-product-allocation.handler.ts new file mode 100644 index 0000000..51f2623 --- /dev/null +++ b/src/space-model/handlers/propogate-space-model-product-allocation.handler.ts @@ -0,0 +1,123 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { PropogateUpdateSpaceModelProductAllocationCommand } from '../commands'; +import { SpaceProductAllocationRepository } from '@app/common/modules/space'; +import { SubspaceModelProductAllocationRepoitory } from '@app/common/modules/space-model'; +import { + SubspaceRepository, + SubspaceProductAllocationRepository, +} from '@app/common/modules/space/repositories/subspace.repository'; + +@CommandHandler(PropogateUpdateSpaceModelProductAllocationCommand) +export class PropogateUpdateSpaceModelProductAllocationHandler + implements ICommandHandler +{ + constructor( + private readonly subspaceRepository: SubspaceRepository, + private readonly subspaceModelProductRepository: SubspaceModelProductAllocationRepoitory, + private readonly subspaceProductRepository: SubspaceProductAllocationRepository, + private readonly spaceProductRepository: SpaceProductAllocationRepository, + ) {} + + async execute( + command: PropogateUpdateSpaceModelProductAllocationCommand, + ): Promise { + const { spaces, updatedAllocations } = command.param; + + try { + if (!updatedAllocations || updatedAllocations.length === 0) { + console.log('No updated allocations found. Exiting execution.'); + return; + } + + console.log(`Processing ${updatedAllocations.length} allocations...`); + + for (const allocation of updatedAllocations) { + try { + if (allocation.allocation) { + const spaceAllocations = await this.spaceProductRepository.find({ + where: { uuid: allocation.allocation.uuid }, + relations: ['tags'], + }); + + if (!spaceAllocations || spaceAllocations.length === 0) { + console.warn( + `No space allocations found for UUID: ${allocation.allocation.uuid}`, + ); + continue; + } + + if (allocation.tagsAdded?.length) { + for (const spaceAllocation of spaceAllocations) { + spaceAllocation.tags.push(...allocation.tagsAdded); + } + await this.spaceProductRepository.save(spaceAllocations); + console.log( + `Added tags to ${spaceAllocations.length} space allocations.`, + ); + } + + if (allocation.tagsRemoved?.length) { + const tagsToRemoveUUIDs = new Set( + allocation.tagsRemoved.map((tag) => tag.uuid), + ); + + for (const spaceAllocation of spaceAllocations) { + spaceAllocation.tags = spaceAllocation.tags.filter( + (tag) => !tagsToRemoveUUIDs.has(tag.uuid), + ); + } + await this.spaceProductRepository.save(spaceAllocations); + console.log( + `Removed tags from ${spaceAllocations.length} space allocations.`, + ); + } + } + + if (allocation.deletedAllocation) { + const spaceAllocations = await this.spaceProductRepository.find({ + where: { uuid: allocation.deletedAllocation.uuid }, + relations: ['tags'], + }); + + if (!spaceAllocations || spaceAllocations.length === 0) { + console.warn( + `No space allocations found to delete for UUID: ${allocation.deletedAllocation.uuid}`, + ); + continue; + } + + await this.spaceProductRepository.remove(spaceAllocations); + console.log( + `Deleted ${spaceAllocations.length} space allocations.`, + ); + } + + if (allocation.newAllocation) { + const newAllocations = spaces.map((space) => + this.spaceProductRepository.create({ + space, + product: allocation.newAllocation.product, + tags: allocation.newAllocation.tags, + inheritedFromModel: allocation.newAllocation, + }), + ); + + await this.spaceProductRepository.save(newAllocations); + console.log( + `Created ${newAllocations.length} new space allocations.`, + ); + } + } catch (error) { + console.error( + `Error processing allocation update: ${JSON.stringify(allocation)}`, + error, + ); + } + } + + console.log('Finished processing all allocations.'); + } catch (error) { + console.error('Unexpected error during execute method:', error); + } + } +} diff --git a/src/space-model/handlers/propogate-subspace-handler.ts b/src/space-model/handlers/propogate-subspace-handler.ts new file mode 100644 index 0000000..d651d85 --- /dev/null +++ b/src/space-model/handlers/propogate-subspace-handler.ts @@ -0,0 +1,286 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { PropogateUpdateSpaceModelCommand } from '../commands'; +import { SpaceProductAllocationRepository } from '@app/common/modules/space'; +import { + SubspaceProductAllocationRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories/subspace.repository'; +import { SubspaceModelProductAllocationRepoitory } from '@app/common/modules/space-model'; +import { In } from 'typeorm'; +import { ISingleSubspaceModel } from '../interfaces'; +import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; +import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { IUpdatedAllocations } from '../interfaces/subspace-product-allocation-update-result.interface'; + +@CommandHandler(PropogateUpdateSpaceModelCommand) +export class PropogateUpdateSpaceModelHandler + implements ICommandHandler +{ + constructor( + private readonly subspaceRepository: SubspaceRepository, + private readonly subspaceModelProductRepository: SubspaceModelProductAllocationRepoitory, + private readonly subspaceProductRepository: SubspaceProductAllocationRepository, + private readonly spaceProductRepository: SpaceProductAllocationRepository, + ) {} + + async execute(command: PropogateUpdateSpaceModelCommand): Promise { + const { subspaceUpdates, spaces } = command.param; + + try { + if (!subspaceUpdates?.subspaceModels?.length) return; // Exit if no updates + if (!spaces?.length) return; // Exit if no spaces + + const subspaceModels = subspaceUpdates.subspaceModels; + + // 🔹 Filter subspace models by action + const subspacesToAdd = subspaceModels.filter( + (m) => m.action === ModifyAction.ADD, + ); + const subspacesToUpdate = subspaceModels.filter( + (m) => m.action === ModifyAction.UPDATE, + ); + const subspacesToDelete = subspaceModels.filter( + (m) => m.action === ModifyAction.DELETE, + ); + + // 1️⃣ Create Subspace Models First (if any) + await Promise.all( + subspacesToAdd.map((m) => this.addSubspaceModel(m, spaces)), + ); + + // 2️⃣ Update Allocations + await this.updateAllocations(subspaceUpdates.updatedAllocations); + + // 3️⃣ Update Existing Subspace Models (if any) + await Promise.all( + subspacesToUpdate.map((m) => this.updateSubspaceModel(m)), + ); + + // 4️⃣ Delete Subspace Models (if any) + await Promise.all( + subspacesToDelete.map((m) => this.deleteSubspaceModel(m, spaces)), + ); + + console.log('Successfully executed PropogateUpdateSpaceModelCommand'); + } catch (error) { + console.error('Error processing subspace model updates', error); + } + } + + async updateAllocations(allocations: IUpdatedAllocations[]) { + try { + for (const allocation of allocations) { + if (!allocation) continue; + + if (allocation.allocation) { + try { + const subspaceAllocations = + await this.subspaceProductRepository.find({ + where: { + inheritedFromModel: { uuid: allocation.allocation.uuid }, + }, + relations: ['tags'], + }); + + if (!subspaceAllocations || subspaceAllocations.length === 0) + continue; + + if (allocation.tagsAdded?.length) { + for (const subspaceAllocation of subspaceAllocations) { + subspaceAllocation.tags.push(...allocation.tagsAdded); + } + await this.subspaceProductRepository.save(subspaceAllocations); + console.log( + `Added tags to ${subspaceAllocations.length} subspace allocations.`, + ); + } + + if (allocation.tagsRemoved?.length) { + const tagsToRemoveUUIDs = allocation.tagsRemoved.map( + (tag) => tag.uuid, + ); + + for (const subspaceAllocation of subspaceAllocations) { + subspaceAllocation.tags = subspaceAllocation.tags.filter( + (tag) => !tagsToRemoveUUIDs.includes(tag.uuid), + ); + } + await this.subspaceProductRepository.save(subspaceAllocations); + console.log( + `Removed tags from ${subspaceAllocations.length} subspace allocations.`, + ); + } + } catch (error) { + console.error('Error processing allocation update:', error); + } + } + + if (allocation.newAllocation) { + try { + const subspaceModel = allocation.newAllocation.subspaceModel; + const subspaces = await this.subspaceRepository.find({ + where: { subSpaceModel: { uuid: subspaceModel.uuid } }, + }); + + if (!subspaces || subspaces.length === 0) continue; + + const newAllocations = subspaces.map((subspace) => + this.subspaceProductRepository.create({ + product: allocation.newAllocation.product, + tags: allocation.newAllocation.tags, + subspace, + inheritedFromModel: allocation.newAllocation, + }), + ); + + await this.subspaceProductRepository.save(newAllocations); + console.log( + `Created ${newAllocations.length} new subspace allocations.`, + ); + } catch (error) { + console.error('Error creating new subspace allocation:', error); + } + } + + if (allocation.deletedAllocation) { + try { + const subspaceAllocations = + await this.subspaceProductRepository.find({ + where: { + inheritedFromModel: { + uuid: allocation.deletedAllocation.uuid, + }, + }, + }); + + if (!subspaceAllocations || subspaceAllocations.length === 0) + continue; + + await this.subspaceProductRepository.remove(subspaceAllocations); + console.log( + `Deleted ${subspaceAllocations.length} subspace allocations.`, + ); + } catch (error) { + console.error('Error deleting subspace allocation:', error); + } + } + } + } catch (error) { + console.error('Error in updateAllocations method:', error); + } + } + + async addSubspaceModel( + subspaceModel: ISingleSubspaceModel, + spaces: SpaceEntity[], + ) { + const subspaceModelAllocations = + await this.subspaceModelProductRepository.find({ + where: { + subspaceModel: { + uuid: subspaceModel.subspaceModel.uuid, + }, + }, + relations: ['tags', 'product'], + }); + + for (const space of spaces) { + const subspace = this.subspaceRepository.create({ + subspaceName: subspaceModel.subspaceModel.subspaceName, + space: space, + }); + + subspace.subSpaceModel = subspaceModel.subspaceModel; + await this.subspaceRepository.save(subspace); + + if (subspaceModelAllocations?.length > 0) { + for (const allocation of subspaceModelAllocations) { + const subspaceAllocation = this.subspaceProductRepository.create({ + subspace: subspace, + product: allocation.product, + tags: allocation.tags, + inheritedFromModel: allocation, + }); + await this.subspaceProductRepository.save(subspaceAllocation); + } + } + } + } + + async deleteSubspaceModel( + subspaceModel: ISingleSubspaceModel, + spaces: SpaceEntity[], + ) { + const subspaces = await this.subspaceRepository.find({ + where: { + subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid }, + disabled: false, + }, + relations: [ + 'productAllocations', + 'productAllocations.product', + 'productAllocations.tags', + ], + }); + + if (!subspaces.length) { + return; + } + + const allocationUuidsToRemove = subspaces.flatMap((subspace) => + subspace.productAllocations.map((allocation) => allocation.uuid), + ); + + if (allocationUuidsToRemove.length) { + await this.subspaceProductRepository.delete(allocationUuidsToRemove); + } + + await this.subspaceRepository.update( + { uuid: In(subspaces.map((s) => s.uuid)) }, + { disabled: true }, + ); + + const relocatedAllocations = subspaceModel.relocatedAllocations || []; + + if (!relocatedAllocations.length) { + return; + } + + for (const space of spaces) { + for (const { allocation, tags = [] } of relocatedAllocations) { + const spaceAllocation = await this.spaceProductRepository.findOne({ + where: { + inheritedFromModel: { uuid: allocation.uuid }, + space: { uuid: space.uuid }, + }, + relations: ['tags'], + }); + + if (spaceAllocation) { + if (tags.length) { + spaceAllocation.tags.push(...tags); + await this.spaceProductRepository.save(spaceAllocation); + } + } else { + const newSpaceAllocation = this.spaceProductRepository.create({ + space, + inheritedFromModel: allocation, + tags: allocation.tags, + product: allocation.product, + }); + await this.spaceProductRepository.save(newSpaceAllocation); + } + } + } + } + + async updateSubspaceModel(subspaceModel: ISingleSubspaceModel) { + return this.subspaceRepository.update( + { + subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid }, + disabled: false, + }, + { subspaceName: subspaceModel.subspaceModel.subspaceName }, + ); + } +} diff --git a/src/space-model/interfaces/index.ts b/src/space-model/interfaces/index.ts index 6d55822..61561f8 100644 --- a/src/space-model/interfaces/index.ts +++ b/src/space-model/interfaces/index.ts @@ -1,3 +1,4 @@ export * from './update-subspace.interface'; export * from './modify-subspace.interface'; export * from './single-subspace.interface'; +export * from './space-product-allocation.interface'; diff --git a/src/space-model/interfaces/single-subspace.interface.ts b/src/space-model/interfaces/single-subspace.interface.ts index f4daac3..76fce50 100644 --- a/src/space-model/interfaces/single-subspace.interface.ts +++ b/src/space-model/interfaces/single-subspace.interface.ts @@ -5,6 +5,7 @@ import { import { ModifyTagModelDto } from '../dtos'; import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { NewTagEntity } from '@app/common/modules/tag'; +import { IUpdatedAllocations } from './subspace-product-allocation-update-result.interface'; export interface IRelocatedAllocation { allocation: SpaceModelProductAllocationEntity; @@ -16,3 +17,8 @@ export interface ISingleSubspaceModel { tags?: ModifyTagModelDto[]; relocatedAllocations?: IRelocatedAllocation[]; } + +export interface ISubspaceModelUpdates { + subspaceModels: ISingleSubspaceModel[]; + updatedAllocations: IUpdatedAllocations[]; +} diff --git a/src/space-model/interfaces/space-product-allocation.interface.ts b/src/space-model/interfaces/space-product-allocation.interface.ts new file mode 100644 index 0000000..fe8e3fc --- /dev/null +++ b/src/space-model/interfaces/space-product-allocation.interface.ts @@ -0,0 +1,10 @@ +import { SpaceModelProductAllocationEntity } from '@app/common/modules/space-model'; +import { NewTagEntity } from '@app/common/modules/tag'; + +export type IUpdatedSpaceAllocations = { + allocation?: SpaceModelProductAllocationEntity; + tagsRemoved?: NewTagEntity[]; + tagsAdded?: NewTagEntity[]; + newAllocation?: SpaceModelProductAllocationEntity; + deletedAllocation?: SpaceModelProductAllocationEntity; +}; diff --git a/src/space-model/services/space-model-product-allocation.service.ts b/src/space-model/services/space-model-product-allocation.service.ts index aa2c3da..d8f78a6 100644 --- a/src/space-model/services/space-model-product-allocation.service.ts +++ b/src/space-model/services/space-model-product-allocation.service.ts @@ -15,6 +15,7 @@ import { NewTagEntity } from '@app/common/modules/tag'; import { ProductEntity } from '@app/common/modules/product/entities'; import { SpaceRepository } from '@app/common/modules/space'; import { ProjectEntity } from '@app/common/modules/project/entities'; +import { IUpdatedSpaceAllocations } from '../interfaces'; @Injectable() export class SpaceModelProductAllocationService { @@ -30,10 +31,12 @@ export class SpaceModelProductAllocationService { tags: ProcessTagDto[], queryRunner?: QueryRunner, modifySubspaceModels?: ModifySubspaceModelDto[], - ): Promise { + ): Promise { try { if (!tags.length) return []; + const allocationUpdates: IUpdatedSpaceAllocations[] = []; + const processedTags = await this.tagService.processTags( tags, projectUuid, @@ -108,8 +111,15 @@ export class SpaceModelProductAllocationService { if (!allocation) { allocation = this.createNewAllocation(spaceModel, tag, queryRunner); productAllocations.push(allocation); + allocationUpdates.push({ + newAllocation: allocation, + }); } else if (!allocation.tags.some((t) => t.uuid === tag.uuid)) { allocation.tags.push(tag); + allocationUpdates.push({ + allocation: allocation, + tagsAdded: [tag], + }); await this.saveAllocation(allocation, queryRunner); } } @@ -119,7 +129,7 @@ export class SpaceModelProductAllocationService { await this.saveAllocations(productAllocations, queryRunner); } - return productAllocations; + return allocationUpdates; } catch (error) { throw this.handleError(error, 'Failed to create product allocations'); } @@ -131,7 +141,7 @@ export class SpaceModelProductAllocationService { spaceModel: SpaceModelEntity, queryRunner: QueryRunner, modifySubspaceModels?: ModifySubspaceModelDto[], - ): Promise { + ): Promise { try { const addDtos = dtos.filter((dto) => dto.action === ModifyAction.ADD); const deleteDtos = dtos.filter( @@ -144,11 +154,13 @@ export class SpaceModelProductAllocationService { uuid: dto.newTagUuid, })); + // Process added tags const processedTags = await this.tagService.processTags( addTagDtos, project.uuid, queryRunner, ); + const addTagUuidMap = new Map(); processedTags.forEach((tag, index) => { addTagUuidMap.set(tag.uuid, addDtos[index]); @@ -161,6 +173,7 @@ export class SpaceModelProductAllocationService { [...addTagUuids].filter((uuid) => deleteTagUuids.has(uuid)), ); + // Filter out tags that are added and deleted in the same request const filteredDtos = dtos.filter( (dto) => !( @@ -174,7 +187,8 @@ export class SpaceModelProductAllocationService { ), ); - await Promise.all([ + // Process add and delete actions concurrently + const [updatedAllocations, deletedAllocations] = await Promise.all([ this.processAddActions( filteredDtos, project.uuid, @@ -184,6 +198,9 @@ export class SpaceModelProductAllocationService { ), this.processDeleteActions(filteredDtos, queryRunner, spaceModel), ]); + + // Combine results and return + return [...updatedAllocations, ...deletedAllocations]; } catch (error) { throw this.handleError(error, 'Error while updating product allocations'); } @@ -195,7 +212,9 @@ export class SpaceModelProductAllocationService { spaceModel: SpaceModelEntity, queryRunner: QueryRunner, modifySubspaceModels?: ModifySubspaceModelDto[], - ): Promise { + ): Promise { + let allocationUpdates: IUpdatedSpaceAllocations[] = []; + const addDtos: ProcessTagDto[] = dtos .filter((dto) => dto.action === ModifyAction.ADD) .map((dto) => ({ @@ -205,7 +224,7 @@ export class SpaceModelProductAllocationService { })); if (addDtos.length > 0) { - await this.createProductAllocations( + allocationUpdates = await this.createProductAllocations( projectUuid, spaceModel, addDtos, @@ -213,6 +232,7 @@ export class SpaceModelProductAllocationService { modifySubspaceModels, ); } + return allocationUpdates; } private createNewAllocation( @@ -292,11 +312,12 @@ export class SpaceModelProductAllocationService { dtos: ModifyTagModelDto[], queryRunner: QueryRunner, spaceModel: SpaceModelEntity, - ): Promise { + ): Promise { try { if (!dtos || dtos.length === 0) { return; } + let allocationUpdateToPropagate: IUpdatedSpaceAllocations[] = []; const tagUuidsToDelete = dtos .filter((dto) => dto.action === ModifyAction.DELETE && dto.tagUuid) @@ -331,15 +352,26 @@ export class SpaceModelProductAllocationService { (tag) => !tagUuidsToDelete.includes(tag.uuid), ); + const deletedTags = allocation.tags.filter((tag) => + tagUuidsToDelete.includes(tag.uuid), + ); + if (updatedTags.length === allocation.tags.length) { continue; } if (updatedTags.length === 0) { deletedAllocations.push(allocation); + allocationUpdateToPropagate.push({ + deletedAllocation: allocation, + }); } else { allocation.tags = updatedTags; allocationUpdates.push(allocation); + allocationUpdateToPropagate.push({ + allocation: allocation, + tagsRemoved: deletedTags, + }); } } @@ -372,7 +404,7 @@ export class SpaceModelProductAllocationService { ) .execute(); - return deletedAllocations; + return allocationUpdateToPropagate; } catch (error) { throw this.handleError(error, `Failed to delete tags in space model`); } diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index ae2a1fa..cdc0ec6 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -28,6 +28,7 @@ import { SpaceModelProductAllocationService } from './space-model-product-alloca import { PropogateDeleteSpaceModelCommand, PropogateUpdateSpaceModelCommand, + PropogateUpdateSpaceModelProductAllocationCommand, } from '../commands'; import { SpaceProductAllocationRepository, @@ -47,7 +48,11 @@ import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity'; import { SubspaceProductAllocationEntity } from '@app/common/modules/space/entities/subspace/subspace-product-allocation.entity'; import { DeviceEntity } from '@app/common/modules/device/entities'; -import { ISingleSubspaceModel } from '../interfaces'; +import { + ISingleSubspaceModel, + ISubspaceModelUpdates, + IUpdatedSpaceAllocations, +} from '../interfaces'; @Injectable() export class SpaceModelService { @@ -199,7 +204,8 @@ export class SpaceModelService { param.projectUuid, ); await queryRunner.connect(); - let modifiedSubspaces: ISingleSubspaceModel[] = []; + let modifiedSubspaces: ISubspaceModelUpdates; + let modifiedProductAllocations: IUpdatedSpaceAllocations[]; try { await queryRunner.startTransaction(); const spaces = await this.fetchModelSpaces(spaceModel); @@ -230,24 +236,35 @@ export class SpaceModelService { } if (dto.tags) { - await this.spaceModelProductAllocationService.updateProductAllocations( - dto.tags, - project, - spaceModel, - queryRunner, - dto.subspaceModels, - ); + modifiedProductAllocations = + await this.spaceModelProductAllocationService.updateProductAllocations( + dto.tags, + project, + spaceModel, + queryRunner, + dto.subspaceModels, + ); } await queryRunner.commitTransaction(); await this.commandBus.execute( new PropogateUpdateSpaceModelCommand({ spaceModel: spaceModel, - subspaceModels: modifiedSubspaces, + subspaceUpdates: modifiedSubspaces, spaces: spaces, }), ); + if (modifiedProductAllocations?.length > 0) { + await this.commandBus.execute( + new PropogateUpdateSpaceModelProductAllocationCommand({ + spaceModel: spaceModel, + updatedAllocations: modifiedProductAllocations, + spaces: spaces, + }), + ); + } + return new SuccessResponseDto({ message: 'SpaceModel updated successfully', }); diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index e5fa6c1..fcf1175 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -16,6 +16,7 @@ import { SubspaceModelProductAllocationService } from './subspace-model-product- import { IRelocatedAllocation, ISingleSubspaceModel, + ISubspaceModelUpdates, } from 'src/space-model/interfaces'; import { SpaceEntity } from '@app/common/modules/space/entities/space.entity'; import { SubSpaceService } from 'src/space/services/subspace'; @@ -118,7 +119,7 @@ export class SubSpaceModelService { projectUuid: string, spaceTagUpdateDtos?: ModifyTagModelDto[], spaces?: SpaceEntity[], - ): Promise { + ): Promise { try { if (!dtos || dtos.length === 0) { return; @@ -160,13 +161,16 @@ export class SubSpaceModelService { spaceModel, spaceTagUpdateDtos, ); - return [ - createdSubspaces ?? [], - updatedSubspaces ?? [], - deletedSubspaces ?? [], - ] - .filter((arr) => arr.length > 0) - .flat(); + return { + subspaceModels: [ + createdSubspaces ?? [], + updatedSubspaces ?? [], + deletedSubspaces ?? [], + ] + .filter((arr) => arr.length > 0) + .flat(), + updatedAllocations: updatedAllocations, + }; } catch (error) { console.error('Error in modifySubspaceModels:', error); throw new HttpException( diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index d49f590..41aebed 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -15,6 +15,7 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; import { PropogateDeleteSpaceModelHandler, PropogateUpdateSpaceModelHandler, + PropogateUpdateSpaceModelProductAllocationHandler, } from './handlers'; import { CqrsModule } from '@nestjs/cqrs'; import { @@ -60,6 +61,7 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie const CommandHandlers = [ PropogateUpdateSpaceModelHandler, PropogateDeleteSpaceModelHandler, + PropogateUpdateSpaceModelProductAllocationHandler, ]; @Module({