This commit is contained in:
mahyargdz
2025-09-14 15:15:03 +03:30
commit be82059172
534 changed files with 51310 additions and 0 deletions
+156
View File
@@ -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 };