@tomei/product
Version:
NestJS package for product module
915 lines (823 loc) • 24.8 kB
text/typescript
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;
}
}
}