UNPKG

@tomei/product

Version:

NestJS package for product module

915 lines (823 loc) 24.8 kB
import { Op } from 'sequelize'; import { ProductVariantStatus, ProductVariantType, } from '../../enum/product-variant.enum'; import { YN } from '../../enum/yn.enum'; import { ProductVariantRepository } from './variant-product.repository'; import { ClassError, ObjectBase } from '@tomei/general'; import { IProductVariantAttr } from '../../interfaces/product-variant-attr.interface'; import { ApplicationConfig } from '@tomei/config'; import { LoginUser } from '@tomei/sso'; import { ProductVariantWithInventoryModel } from '../../entities/product-variant-with-inventory.entity'; import { ProductModel } from '../../entities/product.entity'; import cuid from '../../helpers/cuid'; import { ActionEnum, Activity } from '@tomei/activity-history'; import { IProductVariantUpdate } from '../../interfaces/product-variant-update.interface'; import checkifUserHasPrivilege from '../../helpers/privilege-checking'; export class ProductVariant extends ObjectBase { ObjectId: string; ObjectName: string; TableName: string; ObjectType = 'ProductVariant'; private _ProductId: string; private _Name: string; private _Description: string; private _SKU: string; private _Size: string; private _Colour: string; private _Type: ProductVariantType; private _Level = 0; private _ParentId: string | null = null; private _MinWeight: number; private _MaxWeight: number; private _MinWidth: number; private _MaxWidth: number; private _MinHeight: number; private _MaxHeight: number; private _MinLength: number; private _MaxLength: number; private _Status: ProductVariantStatus = ProductVariantStatus.ACTIVE; private _UpdatedSSYN: YN = YN.N; private _CreatedAt: Date; private _UpdatedAt: Date; private _CreatedById: string; private _UpdatedById: string; private static _Repo = new ProductVariantRepository(); get VariantId(): string { return this.ObjectId; } set VariantId(VariantId: string) { this.ObjectId = VariantId; } get Name(): string { return this._Name; } set Name(Name: string) { this._Name = Name; } get Description(): string { return this._Description; } set Description(Description: string) { this._Description = Description; } get Type(): ProductVariantType { return this._Type; } set Type(Type: ProductVariantType) { this._Type = Type; } get ProductId(): string { return this._ProductId; } set ProductId(ProductId: string) { this._ProductId = ProductId; } get Level(): number { return this._Level; } set Level(Level: number) { this._Level = Level; } get CreatedAt(): Date { return this._CreatedAt; } get UpdatedAt(): Date { return this._UpdatedAt; } get CreatedById(): string { return this._CreatedById; } get UpdatedById(): string { return this._UpdatedById; } get UpdatedSSYN(): YN { return this._UpdatedSSYN; } get SKU(): string { return this._SKU; } get Size(): string { return this._Size; } get Colour(): string { return this._Colour; } get MinWeight(): number { return this._MinWeight; } get MaxWeight(): number { return this._MaxWeight; } get MinWidth(): number { return this._MinWidth; } get MaxWidth(): number { return this._MaxWidth; } get MinHeight(): number { return this._MinHeight; } get MaxHeight(): number { return this._MaxHeight; } get MinLength(): number { return this._MinLength; } get MaxLength(): number { return this._MaxLength; } get ParentId(): string | null { return this._ParentId; } get Status(): ProductVariantStatus { return this._Status; } set Status(Status: ProductVariantStatus) { this._Status = Status; } async setSKU(SKU: string) { try { const where = { SKU: SKU, }; if (this.VariantId) { where['VariantId'] = { [Op.ne]: this.VariantId, }; } const variant = await ProductVariant._Repo.findOne({ where: where, }); if (variant) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg03', 'SKU must be unique.', ); } this._SKU = SKU; } catch (error) { throw error; } } async setParentId(ParentId: string) { try { if (this.Level === 0) { this._ParentId = null; } if (this.Level >= 1) { const variant = await ProductVariant._Repo.findOne({ where: { VariantId: ParentId, }, }); if (!variant) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg04', 'ParentId must be existing Product Variant Id.', ); } this._ParentId = ParentId; } } catch (error) { throw error; } } isProductIdMissing(): boolean { if (!this.ProductId) { return true; } return false; } async setSize(Size: string) { try { if (this.isProductIdMissing()) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg02', 'ProductId is missing.', ); } const where: any = { ProductId: this.ProductId, Level: this.Level, ParentId: this.ParentId, }; if (this.VariantId) { where['VariantId'] = { [Op.ne]: this.VariantId, }; } const variants = await ProductVariant._Repo.findAll({ where, }); variants.forEach((variant) => { if (variant.Size === Size) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg11', 'Duplicate Size found on other variant of the same product.', ); } }); this._Size = Size; } catch (error) { throw error; } } async setColour(Colour: string) { try { if (this.isProductIdMissing()) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg02', 'ProductId is missing.', ); } const where: any = { ProductId: this.ProductId, Level: this.Level, ParentId: this.ParentId, }; if (this.VariantId) { where['VariantId'] = { [Op.ne]: this.VariantId, }; } const variants = await ProductVariant._Repo.findAll({ where, }); variants.forEach((variant) => { if (variant.Colour === Colour) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg12', 'Duplicate Colour found on other variant of the same product.', ); } }); this._Colour = Colour; } catch (error) { throw error; } } async setMinMax( type: | ProductVariantType.HEIGHT | ProductVariantType.LENGTH | ProductVariantType.WEIGHT | ProductVariantType.WIDTH, min: number, max: number, ) { try { if (this.isProductIdMissing()) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg02', 'ProductId is missing.', ); } const where: any = { ProductId: this.ProductId, Level: this.Level, ParentId: this.ParentId, [Op.or]: [ { ['Min' + type]: { [Op.between]: [min[0], max[1]] } }, { ['Max' + type]: { [Op.between]: [min[0], max[1]] } }, ], }; if (this.VariantId) { where['VariantId'] = { [Op.ne]: this.VariantId, }; } const variants = await ProductVariant._Repo.findAll({ where, }); if (variants) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg13', 'Min Max fields values cannot overlap.', ); } switch (this.Type) { case ProductVariantType.WEIGHT: this._MinWeight = min; this._MaxWeight = max; break; case ProductVariantType.WIDTH: this._MinWidth = min; this._MaxWidth = max; break; case ProductVariantType.HEIGHT: this._MinHeight = min; this._MaxHeight = max; break; case ProductVariantType.LENGTH: this._MinLength = min; this._MaxLength = max; break; default: break; } } catch (error) { throw error; } } validateType() { switch (this.Type) { case ProductVariantType.SIZE: if (this.Size === null) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg05', 'Size is compulsory.', ); } break; case ProductVariantType.COLOUR: if (this.Colour === null) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg06', 'Colour is compulsory.', ); } break; case ProductVariantType.WEIGHT: if (this.MinWeight === null || this.MaxWeight === null) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg07', 'MinWeight and MaxWeight fields must be compulsory.', ); } break; case ProductVariantType.WIDTH: if (this.MinWidth === null || this.MaxWidth === null) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg08', 'MinWidth and MaxWidth fields must be compulsory.', ); } break; case ProductVariantType.HEIGHT: if (this.MinHeight === null || this.MaxHeight === null) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg09', 'MinHeight and MaxHeight fields must be compulsory.', ); } break; case ProductVariantType.LENGTH: if (this.MinLength === null || this.MaxLength === null) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg10', 'MinLength and MaxLength fields must be compulsory.', ); } break; default: break; } } protected constructor(variantAttr?: IProductVariantAttr) { super(); if (variantAttr) { //populate all the attributes this.VariantId = variantAttr.VariantId; this.ProductId = variantAttr.ProductId; this.Name = variantAttr.Name; this.Description = variantAttr.Description; this._SKU = variantAttr.SKU; this._Size = variantAttr.Size; this._Colour = variantAttr.Colour; this.Type = variantAttr.Type; this.Level = variantAttr.Level; this._ParentId = variantAttr.ParentId; this._MinWeight = variantAttr.MinWeight; this._MaxWeight = variantAttr.MaxWeight; this._MinWidth = variantAttr.MinWidth; this._MaxWidth = variantAttr.MaxWidth; this._MinHeight = variantAttr.MinHeight; this._MaxHeight = variantAttr.MaxHeight; this._MinLength = variantAttr.MinLength; this._MaxLength = variantAttr.MaxLength; this._Status = variantAttr.Status; this._UpdatedSSYN = variantAttr.UpdatedSSYN; this._CreatedAt = variantAttr.CreatedAt; this._UpdatedAt = variantAttr.UpdatedAt; this._CreatedById = variantAttr.CreatedById; this._UpdatedById = variantAttr.UpdatedById; } } public static async init(variantId?: string, dbTransaction?: any) { try { if (variantId) { const variant = await ProductVariant._Repo.findOne({ where: { VariantId: variantId, }, transaction: dbTransaction, }); if (!variant) { throw new Error(`Variant not found.`); } const data: IProductVariantAttr = { VariantId: variant.VariantId, ProductId: variant.ProductId, Name: variant.Name, Description: variant.Description, SKU: variant.SKU, Size: variant.Size, Colour: variant.Colour, Type: variant.Type, Level: variant.Level, ParentId: variant.ParentId, MinWeight: variant.MinWeight, MaxWeight: variant.MaxWeight, MinWidth: variant.MinWidth, MaxWidth: variant.MaxWidth, MinHeight: variant.MinHeight, MaxHeight: variant.MaxHeight, MinLength: variant.MinLength, MaxLength: variant.MaxLength, Status: variant.Status, CreatedById: variant.CreatedById, CreatedAt: variant.CreatedAt, UpdatedById: variant.UpdatedById, UpdatedAt: variant.UpdatedAt, UpdatedSSYN: variant.UpdatedSSYN, }; return new ProductVariant(data); } else { return new ProductVariant(); } } catch (error) { throw error; } } public static async findAll( productId: string, loginUser: LoginUser, page?: number, row?: number, dbTransaction?: any, ) { try { // Privilege checking const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'ProductVariant - List', ); if (!isPrivileged) { throw new Error('You do not have permission to list product variant.'); } let options: any = { where: { ProductId: productId, }, include: [ { model: ProductVariantWithInventoryModel, }, { model: ProductModel, }, ], transaction: dbTransaction, }; if (page && row) { options = { ...options, offset: (page - 1) * row, limit: row, order: [['CreatedAt', 'DESC']], }; } const productBrand = await this._Repo.findAndCountAll(options); const data = productBrand.rows.map((variant) => { return { VariantId: variant.VariantId, ProductId: variant.ProductId, Name: variant.Name, Description: variant.Description, SKU: variant.SKU, Size: variant.Size, Colour: variant.Colour, Type: variant.Type, Level: variant.Level, ParentId: variant.ParentId, MinWeight: variant.MinWeight, MaxWeight: variant.MaxWeight, MinWidth: variant.MinWidth, MaxWidth: variant.MaxWidth, MinHeight: variant.MinHeight, MaxHeight: variant.MaxHeight, MinLength: variant.MinLength, MaxLength: variant.MaxLength, Status: variant.Status, CreatedById: variant.CreatedById, CreatedAt: variant.CreatedAt, UpdatedById: variant.UpdatedById, UpdatedAt: variant.UpdatedAt, UpdatedSSYN: variant.UpdatedSSYN, TotalUnitsAvailable: variant.ProductVariantWithInventory.TotalUnitsAvailable, TotalUnitsInCurrentOrder: variant.ProductVariantWithInventory.TotalUnitsInCurrentOrder, TotalUnitsSold: variant.ProductVariantWithInventory.TotalUnitsSold, TotalUnitsReserved: variant.ProductVariantWithInventory.TotalUnitsReserved, TotalUnitsInTransit: variant.ProductVariantWithInventory.TotalUnitsInTransit, TotalUnitsOnConsignment: variant.ProductVariantWithInventory.TotalUnitsOnConsignment, TotalUnitsBackOrdered: variant.ProductVariantWithInventory.TotalUnitsBackOrdered, TotalUnitsPreOrdered: variant.ProductVariantWithInventory.TotalUnitsPreOrdered, StockLowAlertLevel: variant.ProductVariantWithInventory.StockLowAlertLevel, StockReorderLevel: variant.ProductVariantWithInventory.StockReorderLevel, BufferStockLevel: variant.ProductVariantWithInventory.BufferStockLevel, Products: { ...variant.Product, }, }; }); return { count: productBrand.count, rows: data, }; } catch (error) { throw error; } } public async create(loginUser: LoginUser, dbTransaction?: any) { try { // Privilege checking const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'ProductVariant - Create', ); if (!isPrivileged) { throw new Error( 'You do not have permission to create product variant.', ); } if (!this.ProductId) { throw new ClassError( 'ProductVariant', 'ProductVariantErrMsg02', 'ProductId is missing.', ); } this.validateType(); this.VariantId = cuid(); this._CreatedAt = new Date(); this._UpdatedAt = new Date(); this._CreatedById = loginUser.ObjectId; this._UpdatedById = loginUser.ObjectId; const data = { VariantId: this.VariantId, ProductId: this.ProductId, Name: this.Name, Description: this.Description, SKU: this.SKU, Size: this.Size, Colour: this.Colour, Type: this.Type, Level: this.Level, ParentId: this.ParentId, MinWeight: this.MinWeight, MaxWeight: this.MaxWeight, MinWidth: this.MinWidth, MaxWidth: this.MaxWidth, MinHeight: this.MinHeight, MaxHeight: this.MaxHeight, MinLength: this.MinLength, MaxLength: this.MaxLength, Status: this.Status, CreatedById: this.CreatedById, CreatedAt: this.CreatedAt, UpdatedById: this.UpdatedById, UpdatedAt: this.UpdatedAt, UpdatedSSYN: this.UpdatedSSYN, }; await ProductVariant._Repo.create(data, { transaction: dbTransaction, }); //Record Activity History const activity = new Activity(); activity.ActivityId = cuid(); activity.Action = ActionEnum.CREATE; activity.Description = 'Add new product variant'; activity.EntityType = 'ProductVariant'; activity.EntityId = data.VariantId; activity.EntityValueBefore = JSON.stringify({}); activity.EntityValueAfter = JSON.stringify(data); await activity.create(loginUser.ObjectId, dbTransaction); return this; } catch (error) { throw error; } } public async update( payload: IProductVariantUpdate, loginUser: LoginUser, dbTransaction?: any, ) { try { await checkifUserHasPrivilege( loginUser, 'ProductVariant - Update', 'You do not have permission to update product variant.', ); const entityValueBefore = { VariantId: this.VariantId, ProductId: this.ProductId, Name: this.Name, Description: this.Description, SKU: this.SKU, Size: this.Size, Colour: this.Colour, Type: this.Type, Level: this.Level, ParentId: this.ParentId, MinWeight: this.MinWeight, MaxWeight: this.MaxWeight, MinWidth: this.MinWidth, MaxWidth: this.MaxWidth, MinHeight: this.MinHeight, MaxHeight: this.MaxHeight, MinLength: this.MinLength, MaxLength: this.MaxLength, UpdatedSSYN: this.UpdatedSSYN, Status: this.Status, CreatedById: this.CreatedById, CreatedAt: this.CreatedAt, UpdatedById: this.UpdatedById, UpdatedAt: this.UpdatedAt, }; this.Name = payload.Name; this.Description = payload.Description; if (this.SKU !== payload.SKU) { await this.setSKU(payload.SKU); } this.Level = payload.Level; if (this.ParentId !== payload.ParentId) { await this.setParentId(payload.ParentId); } const isTypeChanged = this.Type !== payload.Type; this.Type = payload.Type; if (isTypeChanged) { this._Size = null; this._Colour = null; this._MinWeight = null; this._MaxWeight = null; this._MinWidth = null; this._MaxWidth = null; this._MinHeight = null; this._MaxHeight = null; this._MinLength = null; this._MaxLength = null; } switch (this.Type) { case ProductVariantType.COLOUR: await this.setColour(payload.Colour); break; case ProductVariantType.SIZE: await this.setSize(payload.Size); break; case ProductVariantType.HEIGHT: await this.setMinMax(this.Type, payload.MinWeight, payload.MaxHeight); break; case ProductVariantType.LENGTH: await this.setMinMax(this.Type, payload.MinHeight, payload.MaxHeight); break; case ProductVariantType.WIDTH: await this.setMinMax(this.Type, payload.MinHeight, payload.MaxHeight); break; case ProductVariantType.WEIGHT: await this.setMinMax(this.Type, payload.MinHeight, payload.MaxHeight); break; default: break; } this._UpdatedAt = new Date(); this._CreatedById = loginUser.ObjectId; const data: any = { Name: this.Name, Description: this.Description, SKU: this.SKU, Size: this.Size, Colour: this.Colour, Type: this.Type, Level: this.Level, ParentId: this.ParentId, MinWeight: this.MinWeight, MaxWeight: this.MaxWeight, MinWidth: this.MinWidth, MaxWidth: this.MaxWidth, MinHeight: this.MinHeight, MaxHeight: this.MaxHeight, MinLength: this.MinLength, MaxLength: this.MaxLength, UpdatedSSYN: this.UpdatedSSYN, Status: this.Status, CreatedById: this.CreatedById, CreatedAt: this.CreatedAt, UpdatedById: this.UpdatedById, UpdatedAt: this.UpdatedAt, }; await ProductVariant._Repo.update(data, { where: { VariantId: this.VariantId, }, transaction: dbTransaction, }); data.VariantId = this.VariantId; data.ProductId = this.ProductId; //Record Activity History const activity = new Activity(); activity.ActivityId = cuid(); activity.Action = ActionEnum.UPDATE; activity.Description = 'Update product variant'; activity.EntityType = 'ProductVariant'; activity.EntityId = data.VariantId; activity.EntityValueBefore = JSON.stringify(entityValueBefore); activity.EntityValueAfter = JSON.stringify(data); await activity.create(loginUser.ObjectId, dbTransaction); return this; } catch (error) { throw error; } } async delete(loginUser: LoginUser, dbTransaction?: any) { try { await checkifUserHasPrivilege( loginUser, 'ProductVariant - Delete', 'You do not have permission to delete product variant.', ); const entityValueBefore = { VariantId: this.VariantId, ProductId: this.ProductId, Name: this.Name, Description: this.Description, SKU: this.SKU, Size: this.Size, Colour: this.Colour, Type: this.Type, Level: this.Level, ParentId: this.ParentId, MinWeight: this.MinWeight, MaxWeight: this.MaxWeight, MinWidth: this.MinWidth, MaxWidth: this.MaxWidth, MinHeight: this.MinHeight, MaxHeight: this.MaxHeight, MinLength: this.MinLength, MaxLength: this.MaxLength, UpdatedSSYN: this.UpdatedSSYN, Status: this.Status, CreatedById: this.CreatedById, CreatedAt: this.CreatedAt, UpdatedById: this.UpdatedById, UpdatedAt: this.UpdatedAt, }; await ProductVariant._Repo.delete(this.VariantId, dbTransaction); //Record Activity History const activity = new Activity(); activity.ActivityId = cuid(); activity.Action = ActionEnum.DELETE; activity.Description = 'Delete product variant'; activity.EntityType = 'ProductVariant'; activity.EntityId = this.VariantId; activity.EntityValueBefore = JSON.stringify(entityValueBefore); activity.EntityValueAfter = JSON.stringify({}); await activity.create(loginUser.ObjectId, dbTransaction); return { MessageCode: 'ProductVariantDeleted', Message: 'Product Variant is deleted.', }; } catch (error) { throw error; } } }