@chiper-inc/ecommerce-lib
Version:
Chiper Inc Ecommerce Lib
558 lines (533 loc) • 13.7 kB
text/typescript
import { Item, ItemOptions, ItemType } from "./item";
import { Price, PriceOptions } from "./price";
import { New } from "./new";
import { CartEvent } from "../event";
export class Measurement {
constructor(public readonly unit: string, public readonly quantity: number) {}
toJSON() {
return {
unit: this.unit,
quantity: this.quantity,
};
}
}
export type ProductOptions = ItemOptions & {
prices: Price[];
retailPrice: number;
measurement: Measurement;
sku: string;
sponsored: boolean;
referenceId: number;
ledgerAccount: string;
description: string;
lowStock: boolean;
brandName: string;
minQuantity: number;
maxQuantity?: number;
referencePromotionId?: number;
chiperPrice?: number;
oldPrice?: number;
clusterCode?: string;
isKvi?: boolean;
partnerId?: number;
citrusAdId?: string;
};
export type ProductRaw = Omit<ProductOptions, "prices"> & {
prices: PriceOptions[];
type: ItemType;
};
export class Product extends Item {
private _prices: Price[];
public readonly measurement: Measurement;
public readonly sku: string;
public readonly sponsored: boolean;
public readonly referenceId: number;
public readonly ledgerAccount: string;
public readonly description: string;
public readonly lowStock: boolean;
public readonly brandName: string;
public readonly minQuantity: number;
public readonly retailPrice: number;
public readonly _maxQuantity?: number;
public readonly referencePromotionId?: number;
private readonly _chiperPrice?: number;
public readonly oldPrice?: number;
public readonly clusterCode?: string;
public readonly isKvi?: boolean;
public readonly partnerId?: number;
public readonly citrusAdId?: string;
constructor({
stock,
quantity,
multipleQuantity,
name,
image,
id,
warehouseId,
packagingType,
prices,
measurement,
sku,
sponsored,
referenceId,
ledgerAccount,
description,
lowStock,
brandName,
minQuantity,
maxQuantity,
retailPrice,
referencePromotionId,
chiperPrice,
oldPrice,
clusterCode,
isKvi,
partnerId,
citrusAdId
}: ProductOptions, rate:number, isBackend?: boolean, isOffline?:boolean, offlinePrices?:any) {
super(ItemType.PRODUCT, {
stock,
multipleQuantity,
name,
image,
warehouseId,
id,
packagingType,
minQuantity,
measurement
},rate, isBackend, isOffline);
this.citrusAdId = citrusAdId;
if (offlinePrices) {
this._prices = offlinePrices;
}else{
this._prices = prices;
}
this.retailPrice = retailPrice;
this.measurement = measurement;
this.sku = sku;
this.sponsored = sponsored;
this.referenceId = referenceId;
this.ledgerAccount = ledgerAccount;
this.description = description;
this.lowStock = lowStock;
this.brandName = brandName;
this.minQuantity = minQuantity;
this._maxQuantity = maxQuantity;
this.quantity = quantity;
this.referencePromotionId = referencePromotionId;
this._chiperPrice = chiperPrice;
this.oldPrice = oldPrice;
this.clusterCode = clusterCode;
this.isKvi = isKvi;
this.partnerId = partnerId;
}
get prices(): Price[] {
return this._prices;
}
set prices(prices: Price[]) {
const currentPrice = this.oldPrice || this.prices[0].total;
const newPrice = prices[0].total;
this._prices = prices;
this.quantity = this._quantity; // update prices quantities
if (newPrice > currentPrice) {
let dif = newPrice - currentPrice;
this.emit(CartEvent.NEWS, {
id: this.id,
cardType: "changePrice",
productType: this.type,
medium: this.image,
name: this.name,
packagingType: this.packagingType,
previousTotal: currentPrice,
quantity: this.quantity,
stock: this.stock,
total: newPrice,
warehouseId: this.warehouseId,
meta: (dif<0) ? "INCREASE" : "DECREASED"
} as New);
}
}
get maxQuantity(): number | undefined {
let sum: number | undefined = 0;
for (const price of this.prices) {
if (!price.maxQuantity && price.maxQuantity !== 0) {
return undefined;
}
sum += price.maxQuantity;
}
return sum;
}
get total(): number | null {
if (this.quantity == null) return null;
return +this.prices
.reduce(
(total: number, price: Price) => total + (price.extendedTotal ?? 0),
0
)
.toFixed(2);
}
get subtotal(): number | null {
if (this.quantity == null) return null;
return +this.prices
.reduce(
(total: number, price: Price) => total + (price.extendedSubtotal ?? 0),
0
)
.toFixed(2);
}
/**
* Set the quantity and updates the prices quantities
*
* @param quantity the quantity
*/
override set quantity(quantity: number | undefined) {
super.quantity = quantity;
if (quantity === null || !this.prices?.length) return;
for (const [idx, price] of this.prices.entries()) {
if (price.isExpired || quantity === undefined) {
price.quantity = 0;
continue;
}
const before = price.quantity;
price.quantity = price.maxQuantity
? Math.min(price.maxQuantity, quantity)
: quantity;
if (idx > 0 && before === 0 && price.quantity > 0) {
this.emit(CartEvent.PRICE_SCALE_UP, this.prices[idx - 1], price);
}
quantity -= price.quantity;
}
}
override get quantity(): number | undefined {
return super.quantity;
}
static from({
id,
name,
stock,
warehouseId,
medium,
packagingType,
multipleQuantity,
prices,
quantity,
measurement,
sku,
sponsored,
citrusAdId,
referenceId,
ledgerAccount,
description,
lowStock,
brandName,
minQuantity,
maximumQuantity,
chiperPrice,
oldPrice,
clusterCode,
isKvi,
partnerId,
}: any, rate:number, isBackend?: boolean, isOffline?:boolean, offlinePrices?:any) {
const pricesToInsert = (!offlinePrices) ? prices : offlinePrices;
return new Product({
id,
warehouseId,
name,
stock,
image: medium,
packagingType,
minQuantity,
retailPrice: pricesToInsert[0].customerTotal,
multipleQuantity,
prices: pricesToInsert.map(
(p: PriceOptions) =>
new Price({
...p,
expireDate: p.expireDate && new Date(p.expireDate),
})
),
quantity,
measurement,
sku,
sponsored,
citrusAdId,
referenceId,
ledgerAccount,
description,
lowStock,
brandName,
maxQuantity: maximumQuantity,
chiperPrice,
oldPrice,
clusterCode,
isKvi,
partnerId,
}, rate, isBackend, isOffline, offlinePrices);
}
static fromShopCart({
multipleQuantity,
prices,
discountedTotal,
managerPrice,
discountedSubtotal,
stock,
quantity,
medium,
id,
warehouseId,
packagingType,
sku,
sponsored,
citrusAdId,
referenceId,
ledgerAccount,
description,
lowStock,
customerTotal,
brandName,
minQuantity,
customerMeasurement,
customerMeasurementUnit,
name,
}: any, rate: number): Product {
if (prices) {
multipleQuantity =
multipleQuantity || prices[0].values[0].multipleQuantity;
if (discountedTotal) {
prices[0].values[0].discountedPrice = managerPrice;
prices[0].values[0].discountedSubtotal = discountedSubtotal;
prices[0].values[0].discountedTotal = discountedTotal;
}
}
return new Product({
name,
warehouseId,
prices: Price.fromPricing(prices),
stock,
quantity,
multipleQuantity,
maxQuantity: prices[0].maximumQuantity,
retailPrice: customerTotal,
image: medium,
id,
packagingType,
measurement: new Measurement(
customerMeasurementUnit ?? "",
customerMeasurement ?? 1
),
sku,
sponsored,
citrusAdId,
referenceId,
ledgerAccount,
description,
lowStock,
brandName,
minQuantity
}, rate);
}
static fromCatalog({
stock,
multipleQuantity,
name,
medium,
id,
warehouseId,
packagingType,
prices,
primaryPackaging,
sku,
sponsored,
referenceId,
ledgerAccount,
description,
customerPrice,
lowStock = false,
brandName,
minQuantity = 1,
maxQuantity,
maximumQuantity,
discountedMaximumQuantity,
scheduleEndDate,
quantity,
isKvi = false,
partnerId,
citrusAdId
}: any, rate:number): Product {
return new Product({
stock,
multipleQuantity,
name,
image: medium,
id,
warehouseId,
packagingType,
prices: Price.fromCatalogue({
prices,
maximumQuantity,
discountedMaximumQuantity,
scheduleEndDate,
}),
retailPrice: customerPrice,
measurement: primaryPackaging
? new Measurement(
primaryPackaging.measurementUnit,
primaryPackaging.measurement
)
: new Measurement("", 1),
sku,
sponsored,
referenceId,
ledgerAccount,
description,
lowStock,
brandName,
minQuantity,
maxQuantity: maxQuantity ?? maximumQuantity,
quantity,
isKvi,
partnerId,
citrusAdId
}, rate);
}
static fromPromoDetail({
id,
multipleQuantity,
sku,
displayName,
measurementUnit,
salesUnit,
packagingType,
prices,
warehouseId,
stock,
image,
medium,
partnerId
}: any, rate: number): Product {
return new Product({
stock,
multipleQuantity,
name: displayName,
image: image || medium,
id,
warehouseId,
packagingType,
prices: prices.map((p: any) => new Price(p)),
measurement: new Measurement(measurementUnit, salesUnit),
sku,
referenceId: 0,
ledgerAccount: "0",
description: displayName,
lowStock: false,
brandName: "Unknown",
minQuantity: 1,
maxQuantity: undefined,
retailPrice: 0,
sponsored: false,
citrusAdId:undefined,
isKvi: false,
partnerId,
}, rate);
}
get discountExpirationDate(): Date | undefined | null {
const price = this.prices.find((price: Price) => price.discount);
return price?.expireDate;
}
get regularPrice(): number {
const price = this.prices.find((price: Price) => !price.discount);
if (price === undefined) throw new Error("Product has no regular price");
return price.total;
}
get totalDollars(): number | null {
if (this.quantity == null) return null;
return +(this.prices
.reduce(
(total: number, price: Price) => total + (price.extendedTotal ?? 0),
0
) * this.rate)
.toFixed(2);
}
get regularPriceDolar(): number {
const price = this.prices.find((price: Price) => !price.discount);
if (price === undefined) throw new Error("Product has no regular price");
return price.total * this.rate;
}
get discountedPrice(): number | undefined {
return this.prices.find(
(p) => p.discount !== null && p.discount !== undefined && !p.isExpired
)?.total;
}
/** price without taxes */
get price(): number {
const price = this.prices.find((price: Price) => !price.discount);
if (price === undefined) throw new Error("Product has no regular price");
return price.subtotal;
}
get chiperPrice(): number {
return this._chiperPrice || this.regularPrice;
}
increase() {
this.quantity =
this.quantity == null
? this.minQuantity
: this.quantity + this.multipleQuantity;
}
decrease() {
this.quantity =
this.quantity == null
? this.minQuantity
: this.quantity - this.multipleQuantity;
}
clone(): Product {
return new Product({
id: this.id,
warehouseId: this.warehouseId,
name: this.name,
brandName: this.brandName,
description: this.description,
prices: this.prices,
retailPrice: this.retailPrice,
ledgerAccount: this.ledgerAccount,
lowStock: this.lowStock,
maxQuantity: this.maxQuantity,
measurement: this.measurement,
minQuantity: this.minQuantity,
multipleQuantity: this.multipleQuantity,
packagingType: this.packagingType,
quantity: this.quantity,
referenceId: this.referenceId,
sku: this.sku,
image: this.image,
stock: this.stock,
sponsored: this.sponsored,
isKvi: this.isKvi,
partnerId: this.partnerId,
citrusAdId: this.citrusAdId
}, this.rate);
}
// create toJSON method
toJSON() {
return {
...super.toJSON(),
brandName: this.brandName,
description: this.description,
prices: this.prices.map((prices: Price) =>
prices.toJSON(this.retailPrice)
),
retailPrice: this.retailPrice,
ledgerAccount: this.ledgerAccount,
lowStock: this.lowStock,
maxQuantity: this.maxQuantity,
maximumQuantity: this.maxQuantity, // deprecated
measurement: this.measurement,
referenceId: this.referenceId,
sku: this.sku,
sponsored: this.sponsored ? true : false,
citrusAdId: this.citrusAdId ? this.citrusAdId : "",
clusterCode: this.clusterCode,
isKvi: this.isKvi,
partnerId: this.partnerId
};
}
}