first
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
import { HydratedDocument, Types } from "mongoose";
|
||||
|
||||
import { ColorModel } from "../modules/category/models/color.model";
|
||||
import { MeterageModel } from "../modules/category/models/meterage.model";
|
||||
import { SizeModel } from "../modules/category/models/size.model";
|
||||
import { IPriceHistory } from "../modules/product/models/Abstraction/IPriceHistory";
|
||||
import { PriceHistoryModel } from "../modules/product/models/priceHistory.model";
|
||||
import { ProductObserveModel } from "../modules/product/models/productObserve.model";
|
||||
import { IUser } from "../modules/user/models/Abstraction/IUser";
|
||||
import { EmailService } from "../utils/email.service";
|
||||
import { TimeService } from "../utils/time.service"; // Use DI to inject TimeService for better testability
|
||||
|
||||
enum PriceChangeEventType {
|
||||
PRICE_CHANGE = "priceChange",
|
||||
OBSERVER_NOTIFICATION = "observerNotification",
|
||||
}
|
||||
|
||||
type PriceDetails = {
|
||||
product: number;
|
||||
shop: string;
|
||||
selling_price: number;
|
||||
retail_price: number;
|
||||
variantId: string;
|
||||
isSpecial?: boolean;
|
||||
colorId?: number;
|
||||
sizeId?: number;
|
||||
meterageId?: number;
|
||||
};
|
||||
|
||||
type historyDetail = { shop: string; selling_price: number; retail_price: number };
|
||||
|
||||
class PriceChangeEventService extends EventEmitter {
|
||||
private static instance: PriceChangeEventService;
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
this.subscribeToEvents();
|
||||
}
|
||||
//**** */
|
||||
public static getInstance(): PriceChangeEventService {
|
||||
if (!PriceChangeEventService.instance) {
|
||||
PriceChangeEventService.instance = new PriceChangeEventService();
|
||||
}
|
||||
return PriceChangeEventService.instance;
|
||||
}
|
||||
//**** */
|
||||
public emitPriceChange(priceDetails: PriceDetails): void {
|
||||
this.emit(PriceChangeEventType.PRICE_CHANGE, priceDetails);
|
||||
if (priceDetails.isSpecial) {
|
||||
this.emitObserverNotification(priceDetails);
|
||||
}
|
||||
}
|
||||
//**** */
|
||||
private emitObserverNotification(priceDetails: PriceDetails): void {
|
||||
this.emit(PriceChangeEventType.OBSERVER_NOTIFICATION, priceDetails);
|
||||
}
|
||||
//**** */
|
||||
private subscribeToEvents(): void {
|
||||
this.on(PriceChangeEventType.PRICE_CHANGE, this.handlePriceChange);
|
||||
this.on(PriceChangeEventType.OBSERVER_NOTIFICATION, this.handleObserverNotification);
|
||||
}
|
||||
//**** */
|
||||
private async handlePriceChange(priceDetails: PriceDetails): Promise<void> {
|
||||
try {
|
||||
const title = await this.findVariantTitle(priceDetails);
|
||||
const now = TimeService.getCurrentPersianDate();
|
||||
const { colorId, sizeId, meterageId, product, ...history } = priceDetails;
|
||||
|
||||
const existingHistory = await PriceHistoryModel.findOne({ title, product });
|
||||
|
||||
if (existingHistory) {
|
||||
await this.appendPriceHistory(existingHistory, history, now);
|
||||
console.log(`Updated price history for product ${product} with price: ${history.selling_price}`);
|
||||
} else {
|
||||
await this.createPriceHistory(title, product, history, now);
|
||||
console.log(`Created new price history for product ${product} with price: ${history.selling_price}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to record price change:", error);
|
||||
}
|
||||
}
|
||||
//**** */
|
||||
private async handleObserverNotification(priceDetails: PriceDetails): Promise<void> {
|
||||
try {
|
||||
const observers = await this.findObserversForProduct(priceDetails.product, priceDetails.variantId);
|
||||
|
||||
for (const observer of observers) {
|
||||
const user = observer.user as unknown as IUser;
|
||||
const emailContent = this.generateEmailContent(user.fullName, observer.registeredPrice, priceDetails);
|
||||
await EmailService.sendEmail({
|
||||
subject: "تخفیف ویژه",
|
||||
to: user.email,
|
||||
text: emailContent,
|
||||
});
|
||||
console.log(`Sent notification to observer: ${user.email}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to send observer notifications:", error);
|
||||
}
|
||||
}
|
||||
|
||||
//**** */
|
||||
private async findObserversForProduct(productId: number, variantId: string) {
|
||||
return await ProductObserveModel.find({ product: productId, variant: variantId }).populate("user").lean();
|
||||
}
|
||||
//**** */
|
||||
private generateEmailContent(userName: string, observerPrice: number, priceDetails: PriceDetails): string {
|
||||
const priceDiff = observerPrice - priceDetails.selling_price;
|
||||
const message = `کاربر گرامی ${userName},\n\nقیمت محصولی که شما برای آن منتظر شدهاید، به ${priceDetails.selling_price} تغییر کرده است.`;
|
||||
|
||||
if (priceDiff > 0) {
|
||||
return message + `\n\nاین قیمت ${priceDiff} تومان کمتر از قیمت هدف شما است. این فرصت را از دست ندهید!`;
|
||||
} else {
|
||||
return message + `\n\nاین یک پیشنهاد ویژه است که ممکن است برای شما جذاب باشد!`;
|
||||
}
|
||||
}
|
||||
//**** */
|
||||
private async appendPriceHistory(existHistory: HydratedDocument<IPriceHistory>, history: historyDetail, now: string) {
|
||||
existHistory.history.push({
|
||||
...history,
|
||||
date: now,
|
||||
shop: new Types.ObjectId(history.shop),
|
||||
});
|
||||
await existHistory.save();
|
||||
}
|
||||
//**** */
|
||||
private async createPriceHistory(title: string, product: number, history: historyDetail, now: string): Promise<void> {
|
||||
const priceHistory = new PriceHistoryModel({ title, product });
|
||||
priceHistory.history.push({
|
||||
...history,
|
||||
date: now,
|
||||
shop: new Types.ObjectId(history.shop),
|
||||
});
|
||||
await priceHistory.save();
|
||||
}
|
||||
//**** */
|
||||
private async findVariantTitle(priceDetails: PriceDetails): Promise<string> {
|
||||
if (priceDetails.colorId) {
|
||||
const color = await ColorModel.findById(priceDetails.colorId).lean();
|
||||
return color?.name || "Unknown color";
|
||||
} else if (priceDetails.sizeId) {
|
||||
const size = await SizeModel.findById(priceDetails.sizeId).lean();
|
||||
return size?.value || "Unknown size";
|
||||
} else if (priceDetails.meterageId) {
|
||||
const meterage = await MeterageModel.findById(priceDetails.meterageId).lean();
|
||||
return meterage?.value || "Unknown meterage";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const instance = PriceChangeEventService.getInstance();
|
||||
|
||||
export { instance as PriceChangeEvent, PriceChangeEventService, PriceChangeEventType };
|
||||
Reference in New Issue
Block a user