UNPKG

@chiper-inc/ecommerce-lib

Version:
558 lines (533 loc) 13.7 kB
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 }; } }