import { Item, ItemOptions, ItemType } from "./item"; import { Price, PriceOptions } from "./price"; export class Measurement { constructor(public readonly unit: string, public readonly quantity: number) {} } export type ProductOptions = ItemOptions & { prices: Price[]; quantity: number; retailPrice: number; measurement: Measurement; sku: string; sponsored: boolean; referenceId: number; ledgerAccount: string; description: string; lowStock: boolean; brandName: string; minQuantity: number; maxQuantity?: number; referencePromotionId?: number; }; export type ProductRaw = Omit & { prices: PriceOptions[]; type: ItemType; }; export class Product extends Item { public readonly 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; constructor({ stock, quantity, multipleQuantity, name, image, id, warehouseId, packagingType, prices, measurement, sku, sponsored, referenceId, ledgerAccount, description, lowStock, brandName, minQuantity, maxQuantity, retailPrice, referencePromotionId, }: ProductOptions) { super(ItemType.PRODUCT, { stock, multipleQuantity, name, image, warehouseId, id, packagingType, minQuantity, }); 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; } get maxQuantity(): number | undefined { return this._maxQuantity; } get total(): number { return this.prices.reduce( (total: number, price: Price) => total + price.extendedTotal, 0 ); } get subtotal(): number { return this.prices.reduce( (total: number, price: Price) => total + price.extendedSubtotal, 0 ); } /** * Set the quantity and updates the prices quantities * * @param quantity the quantity * @returns the remaining quantity */ override set quantity(quantity: number) { super.quantity = quantity; if (this.prices) { for (const price of this.prices) { if (price.isExpired) price.quantity = 0; else { price.quantity = Math.min(quantity, price.maxQuantity ?? quantity); quantity -= price.quantity; } } } } override get quantity(): number { return super.quantity; } static from({ id, name, stock, warehouseId, medium, packagingType, multipleQuantity, prices, quantity, measurement, sku, sponsored, referenceId, ledgerAccount, description, lowStock, brandName, minQuantity, maximumQuantity, }: any) { return new Product({ id, warehouseId, name, stock, image: medium, packagingType, minQuantity, retailPrice: prices[0].customerTotal, multipleQuantity, prices: prices.map( (p: PriceOptions) => new Price({ ...p, expireDate: p.expireDate && new Date(p.expireDate), }) ), quantity, measurement, sku, sponsored, referenceId, ledgerAccount, description, lowStock, brandName, maxQuantity: maximumQuantity, }); } static fromShopCart({ multipleQuantity, prices, discountedTotal, managerPrice, discountedSubtotal, stock, quantity, medium, id, warehouseId, packagingType, sku, sponsored, referenceId, ledgerAccount, description, lowStock, customerTotal, brandName, minQuantity, customerMeasurement, customerMeasurementUnit, name, }: any): 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, referenceId, ledgerAccount, description, lowStock, brandName, minQuantity, }); } static fromCatalog({ stock, quantity = 0, multipleQuantity, name, medium, id, warehouseId, packagingType, prices, primaryPackaging, sku, sponsored, referenceId, ledgerAccount, description, customerPrice, lowStock = false, brandName, minQuantity = 1, maxQuantity, maximumQuantity, discountedMaximumQuantity, scheduleEndDate, }: any): Product { return new Product({ stock, quantity, 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, }); } static fromPromoDetail({ id, multipleQuantity, sku, displayName, measurementUnit, salesUnit, packagingType, prices, warehouseId, stock, image, }: any) : Product { return new Product({ stock, quantity: 0, multipleQuantity, name: displayName, image, 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, }); } 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 discountedPrice(): number | undefined { return this.prices.find((p) => p.discount !== null && p.discount !== undefined && !p.isExpired)?.total; } increase() { this.quantity = this.quantity + this.multipleQuantity; } decrease() { this.quantity = 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, }); } // 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, measurement: this.measurement, referenceId: this.referenceId, sku: this.sku, sponsored: this.sponsored ? true : false, }; } }