157 lines
4.8 KiB
TypeScript
157 lines
4.8 KiB
TypeScript
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
|
|
@Injectable()
|
|
export class StockNotifyService {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async listForCustomer(customerId: string) {
|
|
const rows = await this.prisma.stockNotifyRequest.findMany({
|
|
where: { customerId },
|
|
orderBy: { createdAt: 'desc' },
|
|
include: {
|
|
product: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
code: true,
|
|
color: true,
|
|
stock: true,
|
|
imageUrlsJson: true,
|
|
catalog: { select: { code: true } },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
return rows.map((r) => this.mapCustomer(r));
|
|
}
|
|
|
|
async listAdmin(activeOnly = true) {
|
|
const rows = await this.prisma.stockNotifyRequest.findMany({
|
|
where: activeOnly ? { active: true } : undefined,
|
|
orderBy: { createdAt: 'desc' },
|
|
include: {
|
|
customer: { select: { firstName: true, lastName: true, phone: true } },
|
|
product: { select: { name: true, code: true, color: true, stock: true } },
|
|
},
|
|
});
|
|
return rows.map((r) => this.map(r));
|
|
}
|
|
|
|
async subscribe(customerId: string, productId: string) {
|
|
const product = await this.prisma.product.findUnique({ where: { id: productId } });
|
|
if (!product) throw new NotFoundException('محصول یافت نشد');
|
|
const row = await this.prisma.stockNotifyRequest.upsert({
|
|
where: { customerId_productId: { customerId, productId } },
|
|
create: { customerId, productId, active: true },
|
|
update: { active: true, notifiedAt: null },
|
|
include: {
|
|
customer: { select: { firstName: true, lastName: true, phone: true } },
|
|
product: { select: { name: true, code: true, color: true, stock: true } },
|
|
},
|
|
});
|
|
return this.map(row);
|
|
}
|
|
|
|
async unsubscribe(customerId: string, productId: string) {
|
|
await this.prisma.stockNotifyRequest.updateMany({
|
|
where: { customerId, productId },
|
|
data: { active: false },
|
|
});
|
|
return { productId, active: false };
|
|
}
|
|
|
|
async markNotified(id: string) {
|
|
const existing = await this.prisma.stockNotifyRequest.findUnique({
|
|
where: { id },
|
|
});
|
|
if (!existing) throw new NotFoundException('درخواست یافت نشد');
|
|
const row = await this.prisma.stockNotifyRequest.update({
|
|
where: { id },
|
|
data: { notifiedAt: new Date(), active: false },
|
|
include: {
|
|
customer: { select: { firstName: true, lastName: true, phone: true } },
|
|
product: { select: { name: true, code: true, color: true, stock: true } },
|
|
},
|
|
});
|
|
|
|
await this.prisma.customerNotification.create({
|
|
data: {
|
|
customerId: row.customerId,
|
|
event: 'stock_available',
|
|
title: 'موجودی محصول',
|
|
body: `محصول ${row.product.name} دوباره موجود شد.`,
|
|
tone: 'success',
|
|
link: `/product?id=${row.productId}`,
|
|
},
|
|
});
|
|
|
|
return this.map(row);
|
|
}
|
|
|
|
private productThumb(imageUrlsJson: unknown): string {
|
|
if (!Array.isArray(imageUrlsJson)) return '';
|
|
const url = imageUrlsJson.find(
|
|
(item): item is string => typeof item === 'string' && item.trim().length > 0,
|
|
);
|
|
return url?.trim() ?? '';
|
|
}
|
|
|
|
private mapCustomer(r: {
|
|
id: string;
|
|
productId: string;
|
|
active: boolean;
|
|
createdAt: Date;
|
|
notifiedAt: Date | null;
|
|
product: {
|
|
id: string;
|
|
name: string;
|
|
code: string;
|
|
color: string;
|
|
stock: number | null;
|
|
imageUrlsJson: unknown;
|
|
catalog: { code: string };
|
|
};
|
|
}) {
|
|
return {
|
|
id: r.id,
|
|
productId: r.productId,
|
|
productName: r.product.name,
|
|
productCode: r.product.code,
|
|
catalogCode: r.product.catalog.code,
|
|
color: r.product.color,
|
|
stock: r.product.stock,
|
|
imageUrl: this.productThumb(r.product.imageUrlsJson),
|
|
active: r.active,
|
|
createdAt: r.createdAt.toISOString(),
|
|
notifiedAt: r.notifiedAt?.toISOString() ?? null,
|
|
};
|
|
}
|
|
|
|
private map(r: {
|
|
id: string;
|
|
customerId: string;
|
|
productId: string;
|
|
active: boolean;
|
|
createdAt: Date;
|
|
notifiedAt: Date | null;
|
|
customer: { firstName: string; lastName: string; phone: string };
|
|
product: { name: string; code: string; color: string; stock: number | null };
|
|
}) {
|
|
return {
|
|
id: r.id,
|
|
customerId: r.customerId,
|
|
customerName: `${r.customer.firstName} ${r.customer.lastName}`.trim(),
|
|
customerPhone: r.customer.phone,
|
|
productId: r.productId,
|
|
productName: r.product.name,
|
|
productCode: r.product.code,
|
|
color: r.product.color,
|
|
stock: r.product.stock,
|
|
active: r.active,
|
|
createdAt: r.createdAt.toISOString(),
|
|
notifiedAt: r.notifiedAt?.toISOString(),
|
|
};
|
|
}
|
|
}
|