aadded product item to space

This commit is contained in:
hannathkadher
2024-12-12 14:37:44 +04:00
parent 72bebe3b06
commit b8590841a8
17 changed files with 328 additions and 102 deletions

View File

@ -1,5 +1,5 @@
export * from './space.entity';
export * from './subspace.entity';
export * from './space-product-item.entity';
export * from './subspace';
export * from './space-product.entity';
export * from './space-product-item.entity';
export * from './space-link.entity';

View File

@ -14,14 +14,13 @@ export class SpaceProductItemEntity extends AbstractEntity<SpaceProductItemDto>
@ManyToOne(() => SpaceProductEntity, (spaceProduct) => spaceProduct.items, {
nullable: false,
})
public spaceProducts: SpaceProductEntity;
public spaceProduct: SpaceProductEntity;
@ManyToOne(
() => SpaceProductItemModelEntity,
(spaceProductItemModel) => spaceProductItemModel.items,
{
nullable: true,
onDelete: 'SET NULL',
},
)
public spaceProductItemModel?: SpaceProductItemModelEntity;

View File

@ -27,7 +27,7 @@ export class SpaceProductEntity extends AbstractEntity<SpaceProductEntity> {
})
productCount: number;
@OneToMany(() => SpaceProductItemEntity, (item) => item.spaceProducts, {
@OneToMany(() => SpaceProductItemEntity, (item) => item.spaceProduct, {
cascade: true,
})
public items: SpaceProductItemEntity[];

View File

@ -11,7 +11,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { UserSpaceEntity } from '../../user/entities';
import { DeviceEntity } from '../../device/entities';
import { CommunityEntity } from '../../community/entities';
import { SubspaceEntity } from './subspace.entity';
import { SubspaceEntity } from './subspace';
import { SpaceLinkEntity } from './space-link.entity';
import { SpaceProductEntity } from './space-product.entity';
import { SceneEntity } from '../../scene/entities';

View File

@ -0,0 +1 @@
export * from './subspace.entity';

View File

@ -1,9 +1,9 @@
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
import { DeviceEntity } from '@app/common/modules/device/entities';
import { SubspaceModelEntity } from '@app/common/modules/space-model';
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceEntity } from '../../device/entities';
import { SpaceEntity } from './space.entity';
import { SubspaceDto } from '../dtos';
import { SubspaceModelEntity } from '../../space-model';
import { SubspaceDto } from '../../dtos';
import { SpaceEntity } from '../space.entity';
@Entity({ name: 'subspace' })
export class SubspaceEntity extends AbstractEntity<SubspaceDto> {

View File

@ -0,0 +1,4 @@
export * from './dtos';
export * from './entities';
export * from './repositories';
export * from './space.repository.module';

View File

@ -1,7 +1,12 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { SpaceProductEntity } from '../entities/space-product.entity';
import { SpaceEntity, SpaceLinkEntity, SubspaceEntity } from '../entities';
import {
SpaceEntity,
SpaceLinkEntity,
SpaceProductItemEntity,
SubspaceEntity,
} from '../entities';
@Injectable()
export class SpaceRepository extends Repository<SpaceEntity> {
@ -28,3 +33,10 @@ export class SpaceProductRepository extends Repository<SpaceProductEntity> {
super(SpaceProductEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SpaceProductItemRepository extends Repository<SpaceProductItemEntity> {
constructor(private dataSource: DataSource) {
super(SpaceProductItemEntity, dataSource.createEntityManager());
}
}

View File

@ -20,13 +20,6 @@ export class SpaceProductItemModelService {
queryRunner: QueryRunner,
) {
await this.validateTags(itemModelDtos, queryRunner, spaceModel);
if (!spaceProductModel) {
throw new HttpException(
'Space product model is required to create product items.',
HttpStatus.BAD_REQUEST,
);
}
try {
const productItems = itemModelDtos.map((dto) =>
queryRunner.manager.create(this.spaceProductItemRepository.target, {

View File

@ -5,3 +5,4 @@ export * from './subspace';
export * from './space-link';
export * from './space-scene.service';
export * from './space-products';
export * from './space-product-items';

View File

@ -0,0 +1 @@
export * from './space-product-items.service';

View File

@ -0,0 +1,112 @@
import {
SpaceEntity,
SpaceProductEntity,
SpaceProductItemRepository,
} from '@app/common/modules/space';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSpaceProductItemDto } from '../../dtos';
import { QueryRunner } from 'typeorm';
import { SpaceProductModelEntity } from '@app/common/modules/space-model';
@Injectable()
export class SpaceProductItemService {
constructor(
private readonly spaceProductItemRepository: SpaceProductItemRepository,
) {}
async createProductItem(
itemModelDtos: CreateSpaceProductItemDto[],
spaceProduct: SpaceProductEntity,
space: SpaceEntity,
queryRunner: QueryRunner,
) {
await this.validateTags(itemModelDtos, queryRunner, space);
try {
const productItems = itemModelDtos.map((dto) =>
queryRunner.manager.create(this.spaceProductItemRepository.target, {
tag: dto.tag,
spaceProduct,
}),
);
await queryRunner.manager.save(productItems);
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message ||
'An unexpected error occurred while creating product items.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async createSpaceProductItemFromModel(
spaceProduct: SpaceProductEntity,
spaceProductModel: SpaceProductModelEntity,
queryRunner: QueryRunner,
) {
const spaceProductItemModels = spaceProductModel.items;
try {
const productItems = spaceProductItemModels.map((model) =>
queryRunner.manager.create(this.spaceProductItemRepository.target, {
tag: model.tag,
spaceProduct,
}),
);
await queryRunner.manager.save(productItems);
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message ||
'An unexpected error occurred while creating product items.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private async validateTags(
itemModelDtos: CreateSpaceProductItemDto[],
queryRunner: QueryRunner,
space: SpaceEntity,
) {
const incomingTags = itemModelDtos.map((item) => item.tag);
const duplicateTags = incomingTags.filter(
(tag, index) => incomingTags.indexOf(tag) !== index,
);
if (duplicateTags.length > 0) {
throw new HttpException(
`Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`,
HttpStatus.BAD_REQUEST,
);
}
const existingTags = await queryRunner.manager.find(
this.spaceProductItemRepository.target,
{
where: { spaceProduct: { space } },
select: ['tag'],
},
);
const existingTagSet = new Set(existingTags.map((item) => item.tag));
const conflictingTags = incomingTags.filter((tag) =>
existingTagSet.has(tag),
);
if (conflictingTags.length > 0) {
throw new HttpException(
`Tags already exist in the model: ${conflictingTags.join(', ')}`,
HttpStatus.CONFLICT,
);
}
}
}

View File

@ -2,96 +2,98 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { ProductRepository } from '@app/common/modules/product/repositories';
import { SpaceEntity } from '@app/common/modules/space/entities';
import { SpaceProductEntity } from '@app/common/modules/space/entities/space-product.entity';
import { SpaceProductRepository } from '@app/common/modules/space/repositories';
import { In } from 'typeorm';
import { In, QueryRunner } from 'typeorm';
import { ProductAssignmentDto } from '../../dtos';
import { SpaceProductItemService } from '../space-product-items';
import { SpaceModelEntity } from '@app/common/modules/space-model';
@Injectable()
export class SpaceProductService {
constructor(
private readonly productRepository: ProductRepository,
private readonly spaceProductRepository: SpaceProductRepository,
private readonly spaceProductItemService: SpaceProductItemService,
) {}
async createProductItemFromModel(
spaceModel: SpaceModelEntity,
space: SpaceEntity,
queryRunner: QueryRunner,
) {
const spaceProductModels = spaceModel.spaceProductModels;
if (!spaceProductModels?.length) return;
const newSpaceProducts = [];
spaceProductModels.map((spaceProductModel) => {
newSpaceProducts.push(
queryRunner.manager.create(SpaceProductEntity, {
space: space,
product: spaceProductModel.product,
productCount: spaceProductModel.productCount,
spaceProductModel: spaceProductModel,
}),
);
});
if (newSpaceProducts.length > 0) {
await queryRunner.manager.save(SpaceProductEntity, newSpaceProducts);
await Promise.all(
newSpaceProducts.map((spaceProduct, index) => {
const spaceProductModel = spaceProductModels[index];
return this.spaceProductItemService.createSpaceProductItemFromModel(
spaceProduct,
spaceProductModel,
queryRunner,
);
}),
);
}
}
async assignProductsToSpace(
space: SpaceEntity,
products: { productId: string; count: number }[],
products: ProductAssignmentDto[],
queryRunner: QueryRunner,
): Promise<SpaceProductEntity[]> {
let updatedProducts: SpaceProductEntity[] = [];
try {
const uniqueProducts = this.validateUniqueProducts(products);
const productEntities = await this.getProductEntities(uniqueProducts);
const existingSpaceProducts = await this.getExistingSpaceProducts(
space,
queryRunner,
);
// Fetch existing space products
const existingSpaceProducts = await this.spaceProductRepository.find({
where: {
space: {
uuid: space.uuid,
},
},
relations: ['product'],
});
const updatedProducts = [];
const newProducts = [];
for (const { productId, count } of uniqueProducts) {
const product = productEntities.get(productId);
if (!product) {
throw new HttpException(
`Product with ID ${productId} not found`,
HttpStatus.NOT_FOUND,
);
}
// Check if product already exists in the space
const existingProduct = existingSpaceProducts.find(
(spaceProduct) => spaceProduct.product.uuid === productId,
if (existingSpaceProducts) {
updatedProducts = await this.updateExistingProducts(
existingSpaceProducts,
uniqueProducts,
productEntities,
queryRunner,
);
if (existingProduct) {
// If count is different, update the existing record
if (existingProduct.productCount !== count) {
existingProduct.productCount = count;
updatedProducts.push(existingProduct);
}
} else {
// Add new product if it doesn't exist
newProducts.push(
this.spaceProductRepository.create({
space,
product,
productCount: count,
}),
);
}
}
// Save updates and new records
if (updatedProducts.length > 0) {
await this.spaceProductRepository.save(updatedProducts);
}
if (newProducts.length > 0) {
await this.spaceProductRepository.save(newProducts);
}
const newProducts = await this.createNewProducts(
uniqueProducts,
productEntities,
space,
queryRunner,
);
return [...updatedProducts, ...newProducts];
} catch (error) {
console.error('Error assigning products to space:', error);
if (!(error instanceof HttpException)) {
throw new HttpException(
'An error occurred while assigning products to the space',
`An error occurred while assigning products to the space ${error}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
throw error;
}
}
private validateUniqueProducts(
products: { productId: string; count: number }[],
): { productId: string; count: number }[] {
products: ProductAssignmentDto[],
): ProductAssignmentDto[] {
const productIds = new Set();
const uniqueProducts = [];
@ -110,15 +112,13 @@ export class SpaceProductService {
}
private async getProductEntities(
products: { productId: string; count: number }[],
products: ProductAssignmentDto[],
): Promise<Map<string, any>> {
try {
const productIds = products.map((p) => p.productId);
const productEntities = await this.productRepository.find({
where: { uuid: In(productIds) },
});
return new Map(productEntities.map((p) => [p.uuid, p]));
} catch (error) {
console.error('Error fetching product entities:', error);
@ -128,4 +128,90 @@ export class SpaceProductService {
);
}
}
private async getExistingSpaceProducts(
space: SpaceEntity,
queryRunner: QueryRunner,
): Promise<SpaceProductEntity[]> {
return queryRunner.manager.find(SpaceProductEntity, {
where: { space: { uuid: space.uuid } },
relations: ['product'],
});
}
private async updateExistingProducts(
existingSpaceProducts: SpaceProductEntity[],
uniqueProducts: ProductAssignmentDto[],
productEntities: Map<string, any>,
queryRunner: QueryRunner,
): Promise<SpaceProductEntity[]> {
const updatedProducts = [];
for (const { productId, count } of uniqueProducts) {
productEntities.get(productId);
const existingProduct = existingSpaceProducts.find(
(spaceProduct) => spaceProduct.product.uuid === productId,
);
if (existingProduct && existingProduct.productCount !== count) {
existingProduct.productCount = count;
updatedProducts.push(existingProduct);
}
}
if (updatedProducts.length > 0) {
await queryRunner.manager.save(SpaceProductEntity, updatedProducts);
}
return updatedProducts;
}
private async createNewProducts(
uniqueSpaceProducts: ProductAssignmentDto[],
productEntities: Map<string, any>,
space: SpaceEntity,
queryRunner: QueryRunner,
): Promise<SpaceProductEntity[]> {
const newProducts = [];
for (const uniqueSpaceProduct of uniqueSpaceProducts) {
const product = productEntities.get(uniqueSpaceProduct.productId);
this.validateProductCount(uniqueSpaceProduct);
newProducts.push(
queryRunner.manager.create(SpaceProductEntity, {
space,
product,
productCount: uniqueSpaceProduct.count,
}),
);
}
if (newProducts.length > 0) {
await queryRunner.manager.save(SpaceProductEntity, newProducts);
await Promise.all(
uniqueSpaceProducts.map((dto, index) => {
const spaceProduct = newProducts[index];
return this.spaceProductItemService.createProductItem(
dto.items,
spaceProduct,
space,
queryRunner,
);
}),
);
}
return newProducts;
}
private validateProductCount(dto: ProductAssignmentDto) {
const productItemCount = dto.items.length;
if (dto.count !== productItemCount) {
throw new HttpException(
`Product count (${dto.count}) does not match the number of items (${productItemCount}) for product ID ${dto.productId}.`,
HttpStatus.BAD_REQUEST,
);
}
}
}

View File

@ -59,7 +59,12 @@ export class ValidationService {
async validateSpaceModel(spaceModelUuid: string): Promise<SpaceModelEntity> {
const spaceModel = await this.spaceModelRepository.findOne({
where: { uuid: spaceModelUuid },
relations: ['subspaceModels'],
relations: [
'subspaceModels',
'spaceProductModels',
'spaceProductModels.product',
'spaceProductModels.items',
],
});
if (!spaceModel) {

View File

@ -63,7 +63,7 @@ export class SpaceService {
: null;
try {
const newSpace = this.spaceRepository.create({
const newSpace = queryRunner.manager.create(SpaceEntity, {
...addSpaceDto,
spaceModel,
parent: parentUuid ? parent : null,
@ -80,13 +80,13 @@ export class SpaceService {
);
}
if (subspaces) {
if (subspaces?.length) {
await this.subSpaceService.createSubspacesFromNames(
subspaces,
newSpace,
queryRunner,
);
} else {
} else if (spaceModel && spaceModel.subspaceModels.length) {
await this.subSpaceService.createSubSpaceFromModel(
spaceModel,
newSpace,
@ -98,6 +98,13 @@ export class SpaceService {
await this.spaceProductService.assignProductsToSpace(
newSpace,
products,
queryRunner,
);
} else if (spaceModel && spaceModel.spaceProductModels.length) {
await this.spaceProductService.createProductItemFromModel(
spaceModel,
newSpace,
queryRunner,
);
}
await queryRunner.commitTransaction();
@ -216,7 +223,12 @@ export class SpaceService {
updateSpaceDto: UpdateSpaceDto,
): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params;
const queryRunner = this.dataSource.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
const space =
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
@ -234,14 +246,16 @@ export class SpaceService {
Object.assign(space, updateSpaceDto, { parent });
// Save the updated space
const updatedSpace = await this.spaceRepository.save(space);
const updatedSpace = await queryRunner.manager.save(space);
if (products && products.length > 0) {
await this.spaceProductService.assignProductsToSpace(
updatedSpace,
products,
queryRunner,
);
}
await queryRunner.commitTransaction();
return new SuccessResponseDto({
message: `Space with ID ${spaceUuid} successfully updated`,
@ -249,6 +263,8 @@ export class SpaceService {
statusCode: HttpStatus.OK,
});
} catch (error) {
await queryRunner.rollbackTransaction();
if (error instanceof HttpException) {
throw error;
}
@ -256,6 +272,8 @@ export class SpaceService {
'An error occurred while updating the space',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
await queryRunner.release();
}
}
@ -348,13 +366,6 @@ export class SpaceService {
return rootSpaces;
}
private throwNotFound(entity: string, uuid: string) {
throw new HttpException(
`${entity} with ID ${uuid} not found`,
HttpStatus.NOT_FOUND,
);
}
private validateSpaceCreation(
spaceModelUuid?: string,
products?: ProductAssignmentDto[],
@ -362,7 +373,7 @@ export class SpaceService {
) {
if (spaceModelUuid && (products?.length || subSpaces?.length)) {
throw new HttpException(
'Space model cannot be assigned with products or subspaces.',
'For space creation choose either space model or products and subspace',
HttpStatus.CONFLICT,
);
}

View File

@ -49,9 +49,7 @@ export class SubSpaceService {
): Promise<void> {
const subSpaces = spaceModel.subspaceModels;
if (!subSpaces || subSpaces.length === 0) {
return;
}
if (!subSpaces?.length) return;
const subspaceData = subSpaces.map((subSpaceModel) => ({
subspaceName: subSpaceModel.subspaceName,
@ -79,12 +77,11 @@ export class SubSpaceService {
addSubspaceDto: AddSubspaceDto,
params: GetSpaceParam,
): Promise<BaseResponseDto> {
const { communityUuid, spaceUuid, projectUuid } = params;
const space =
await this.validationService.validateSpaceWithinCommunityAndProject(
communityUuid,
projectUuid,
spaceUuid,
params.projectUuid,
params.projectUuid,
params.spaceUuid,
);
try {

View File

@ -12,6 +12,7 @@ import {
import {
SpaceDeviceService,
SpaceLinkService,
SpaceProductItemService,
SpaceProductService,
SpaceSceneService,
SpaceService,
@ -24,6 +25,7 @@ import {
SpaceRepository,
SubspaceRepository,
SpaceLinkRepository,
SpaceProductItemRepository,
} from '@app/common/modules/space/repositories';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import {
@ -87,6 +89,8 @@ import { ValidationService } from './services/space-validation.service';
ProjectRepository,
SpaceModelRepository,
SubspaceRepository,
SpaceProductItemService,
SpaceProductItemRepository,
],
exports: [SpaceService],
})