Rebuild order flow: settle after pricing, notifications, and invoice expiry.
Hide prices until admin sends the invoice, enforce status transitions, add customer/admin SMS+in-app notifications, schedule cash invoice reminders, and ship updated dist/prisma-client/deploy-bundle for git-pull deploys. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
import { NotificationTone, OrderStatus } from '@prisma/client';
|
||||
export type CustomerEvent = 'order_submitted' | 'invoice_priced' | 'checks_approved' | 'checks_need_fix' | 'receipt_approved' | 'receipt_rejected' | 'cash_invoice_reminder' | 'cash_invoice_expired' | 'order_preparing' | 'order_ready_to_ship' | 'order_shipped' | 'order_delivered' | 'order_cancelled';
|
||||
type SmsSwitch = 'smsOrderRegistered' | 'smsPricingReady' | 'smsPaymentReminder' | 'smsCheckReview' | 'smsShippingUpdate' | null;
|
||||
export type CustomerEventTemplate = {
|
||||
title: string;
|
||||
body: (ctx: EventContext) => string;
|
||||
sms?: (ctx: EventContext) => string;
|
||||
tone: NotificationTone;
|
||||
smsSwitch: SmsSwitch;
|
||||
};
|
||||
export type EventContext = {
|
||||
orderNumber: string;
|
||||
appName: string;
|
||||
amount?: number | null;
|
||||
note?: string | null;
|
||||
deadline?: string | null;
|
||||
carrier?: string | null;
|
||||
trackingNumber?: string | null;
|
||||
};
|
||||
export declare function eventForStatus(status: OrderStatus): CustomerEvent | null;
|
||||
export declare const CUSTOMER_EVENTS: Record<CustomerEvent, CustomerEventTemplate>;
|
||||
export {};
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CUSTOMER_EVENTS = void 0;
|
||||
exports.eventForStatus = eventForStatus;
|
||||
const client_1 = require("@prisma/client");
|
||||
function money(amount) {
|
||||
if (!amount)
|
||||
return '—';
|
||||
return `${Math.round(amount).toLocaleString('fa-IR')} تومان`;
|
||||
}
|
||||
function eventForStatus(status) {
|
||||
switch (status) {
|
||||
case client_1.OrderStatus.awaiting_settlement:
|
||||
return 'invoice_priced';
|
||||
case client_1.OrderStatus.preparing:
|
||||
case client_1.OrderStatus.final_approved:
|
||||
return 'order_preparing';
|
||||
case client_1.OrderStatus.ready_to_ship:
|
||||
return 'order_ready_to_ship';
|
||||
case client_1.OrderStatus.shipped:
|
||||
return 'order_shipped';
|
||||
case client_1.OrderStatus.delivered:
|
||||
return 'order_delivered';
|
||||
case client_1.OrderStatus.cancelled:
|
||||
case client_1.OrderStatus.rejected:
|
||||
return 'order_cancelled';
|
||||
case client_1.OrderStatus.expired:
|
||||
return 'cash_invoice_expired';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
exports.CUSTOMER_EVENTS = {
|
||||
order_submitted: {
|
||||
title: 'سفارش شما ثبت شد',
|
||||
body: (c) => `سفارش ${c.orderNumber} ثبت شد و برای بررسی و قیمتگذاری به کارشناسان ما رسید.`,
|
||||
sms: (c) => `سفارش ${c.orderNumber} در ${c.appName} ثبت شد. پس از قیمتگذاری، فاکتور برای شما ارسال میشود.`,
|
||||
tone: client_1.NotificationTone.info,
|
||||
smsSwitch: 'smsOrderRegistered',
|
||||
},
|
||||
invoice_priced: {
|
||||
title: 'فاکتور سفارش شما صادر شد',
|
||||
body: (c) => `مبلغ فاکتور سفارش ${c.orderNumber} ${money(c.amount)} است. برای ادامه، روش تسویه را انتخاب کنید.`,
|
||||
sms: (c) => `فاکتور سفارش ${c.orderNumber} به مبلغ ${money(c.amount)} صادر شد. برای انتخاب روش تسویه به پنل ${c.appName} مراجعه کنید.`,
|
||||
tone: client_1.NotificationTone.success,
|
||||
smsSwitch: 'smsPricingReady',
|
||||
},
|
||||
checks_approved: {
|
||||
title: 'چکهای شما تایید شد',
|
||||
body: (c) => `همه چکهای سفارش ${c.orderNumber} تایید شدند.`,
|
||||
sms: (c) => `چکهای سفارش ${c.orderNumber} تایید شد و سفارش به مرحله آمادهسازی میرود.`,
|
||||
tone: client_1.NotificationTone.success,
|
||||
smsSwitch: 'smsCheckReview',
|
||||
},
|
||||
checks_need_fix: {
|
||||
title: 'چک شما نیاز به اصلاح دارد',
|
||||
body: (c) => `یک یا چند چک سفارش ${c.orderNumber} تایید نشد${c.note ? `: ${c.note}` : '.'} لطفاً چکها را اصلاح و مجدداً ارسال کنید.`,
|
||||
sms: (c) => `چک سفارش ${c.orderNumber} تایید نشد. برای اصلاح چکها به پنل ${c.appName} مراجعه کنید.`,
|
||||
tone: client_1.NotificationTone.danger,
|
||||
smsSwitch: 'smsCheckReview',
|
||||
},
|
||||
receipt_approved: {
|
||||
title: 'پرداخت شما تایید شد',
|
||||
body: (c) => `رسید پرداخت سفارش ${c.orderNumber} تایید شد.`,
|
||||
sms: (c) => `رسید پرداخت سفارش ${c.orderNumber} تایید شد. سفارش شما در حال آمادهسازی است.`,
|
||||
tone: client_1.NotificationTone.success,
|
||||
smsSwitch: 'smsPaymentReminder',
|
||||
},
|
||||
receipt_rejected: {
|
||||
title: 'رسید پرداخت تایید نشد',
|
||||
body: (c) => `رسید پرداخت سفارش ${c.orderNumber} تایید نشد${c.note ? `: ${c.note}` : '.'} لطفاً رسید صحیح را ثبت کنید.`,
|
||||
sms: (c) => `رسید پرداخت سفارش ${c.orderNumber} تایید نشد. لطفاً در پنل ${c.appName} رسید را اصلاح کنید.`,
|
||||
tone: client_1.NotificationTone.danger,
|
||||
smsSwitch: 'smsPaymentReminder',
|
||||
},
|
||||
cash_invoice_reminder: {
|
||||
title: 'یادآوری مهلت پرداخت',
|
||||
body: (c) => `مهلت پرداخت فاکتور سفارش ${c.orderNumber} به پایان نزدیک است. مبلغ قابل پرداخت ${money(c.amount)}.`,
|
||||
sms: (c) => `مهلت پرداخت فاکتور سفارش ${c.orderNumber} در حال پایان است. مبلغ ${money(c.amount)}.`,
|
||||
tone: client_1.NotificationTone.warning,
|
||||
smsSwitch: 'smsPaymentReminder',
|
||||
},
|
||||
cash_invoice_expired: {
|
||||
title: 'مهلت پرداخت فاکتور تمام شد',
|
||||
body: (c) => `مهلت پرداخت فاکتور سفارش ${c.orderNumber} به پایان رسید. برای ثبت سفارش تازه با پشتیبانی تماس بگیرید.`,
|
||||
sms: (c) => `مهلت پرداخت فاکتور سفارش ${c.orderNumber} به پایان رسید.`,
|
||||
tone: client_1.NotificationTone.danger,
|
||||
smsSwitch: 'smsPaymentReminder',
|
||||
},
|
||||
order_preparing: {
|
||||
title: 'سفارش در حال آمادهسازی',
|
||||
body: (c) => `سفارش ${c.orderNumber} تایید نهایی شد و در حال آمادهسازی است.`,
|
||||
tone: client_1.NotificationTone.info,
|
||||
smsSwitch: null,
|
||||
},
|
||||
order_ready_to_ship: {
|
||||
title: 'سفارش آماده ارسال است',
|
||||
body: (c) => `سفارش ${c.orderNumber} آماده ارسال شد.`,
|
||||
sms: (c) => `سفارش ${c.orderNumber} آماده ارسال شد.`,
|
||||
tone: client_1.NotificationTone.info,
|
||||
smsSwitch: 'smsShippingUpdate',
|
||||
},
|
||||
order_shipped: {
|
||||
title: 'سفارش ارسال شد',
|
||||
body: (c) => `سفارش ${c.orderNumber} ارسال شد${c.carrier ? ` (${c.carrier})` : ''}${c.trackingNumber ? ` با کد رهگیری ${c.trackingNumber}` : ''}.`,
|
||||
sms: (c) => `سفارش ${c.orderNumber} ارسال شد${c.trackingNumber ? `. کد رهگیری: ${c.trackingNumber}` : '.'}`,
|
||||
tone: client_1.NotificationTone.success,
|
||||
smsSwitch: 'smsShippingUpdate',
|
||||
},
|
||||
order_delivered: {
|
||||
title: 'سفارش تحویل شد',
|
||||
body: (c) => `سفارش ${c.orderNumber} تحویل داده شد. از خرید شما سپاسگزاریم.`,
|
||||
sms: (c) => `سفارش ${c.orderNumber} تحویل داده شد. از خرید شما سپاسگزاریم.`,
|
||||
tone: client_1.NotificationTone.success,
|
||||
smsSwitch: 'smsShippingUpdate',
|
||||
},
|
||||
order_cancelled: {
|
||||
title: 'سفارش لغو شد',
|
||||
body: (c) => `سفارش ${c.orderNumber} لغو شد${c.note ? `: ${c.note}` : '.'} برای اطلاعات بیشتر با پشتیبانی تماس بگیرید.`,
|
||||
sms: (c) => `سفارش ${c.orderNumber} لغو شد.`,
|
||||
tone: client_1.NotificationTone.danger,
|
||||
smsSwitch: null,
|
||||
},
|
||||
};
|
||||
//# sourceMappingURL=notification-events.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"notification-events.js","sourceRoot":"","sources":["../../src/notifications/notification-events.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { NotificationsService } from './notifications.service';
|
||||
import type { AuthPrincipal } from '../common/decorators/current-user.decorator';
|
||||
export declare class CustomerNotificationsController {
|
||||
private readonly notifications;
|
||||
constructor(notifications: NotificationsService);
|
||||
list(user: AuthPrincipal): Promise<{
|
||||
id: string;
|
||||
orderId: string | undefined;
|
||||
event: string;
|
||||
title: string;
|
||||
body: string;
|
||||
tone: import("@prisma/client").$Enums.NotificationTone;
|
||||
link: string | undefined;
|
||||
isRead: boolean;
|
||||
sentAt: string;
|
||||
}[]>;
|
||||
unreadCount(user: AuthPrincipal): Promise<{
|
||||
count: number;
|
||||
}>;
|
||||
markAllRead(user: AuthPrincipal): Promise<{
|
||||
count: number;
|
||||
}>;
|
||||
markRead(user: AuthPrincipal, id: string): Promise<{
|
||||
count: number;
|
||||
}>;
|
||||
}
|
||||
export declare class AdminNotificationsController {
|
||||
private readonly notifications;
|
||||
constructor(notifications: NotificationsService);
|
||||
markAllRead(): Promise<{
|
||||
ok: boolean;
|
||||
}>;
|
||||
markRead(id: string): Promise<{
|
||||
ok: boolean;
|
||||
}>;
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AdminNotificationsController = exports.CustomerNotificationsController = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const notifications_service_1 = require("./notifications.service");
|
||||
const auth_decorators_1 = require("../common/decorators/auth.decorators");
|
||||
const current_user_decorator_1 = require("../common/decorators/current-user.decorator");
|
||||
let CustomerNotificationsController = class CustomerNotificationsController {
|
||||
notifications;
|
||||
constructor(notifications) {
|
||||
this.notifications = notifications;
|
||||
}
|
||||
list(user) {
|
||||
return this.notifications.listForCustomer(user.id);
|
||||
}
|
||||
unreadCount(user) {
|
||||
return this.notifications.unreadCount(user.id);
|
||||
}
|
||||
markAllRead(user) {
|
||||
return this.notifications.markAllRead(user.id);
|
||||
}
|
||||
markRead(user, id) {
|
||||
return this.notifications.markRead(user.id, id);
|
||||
}
|
||||
};
|
||||
exports.CustomerNotificationsController = CustomerNotificationsController;
|
||||
__decorate([
|
||||
(0, common_1.Get)(),
|
||||
__param(0, (0, current_user_decorator_1.CurrentUser)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CustomerNotificationsController.prototype, "list", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('unread-count'),
|
||||
__param(0, (0, current_user_decorator_1.CurrentUser)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CustomerNotificationsController.prototype, "unreadCount", null);
|
||||
__decorate([
|
||||
(0, common_1.Patch)('read-all'),
|
||||
__param(0, (0, current_user_decorator_1.CurrentUser)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CustomerNotificationsController.prototype, "markAllRead", null);
|
||||
__decorate([
|
||||
(0, common_1.Patch)(':id/read'),
|
||||
__param(0, (0, current_user_decorator_1.CurrentUser)()),
|
||||
__param(1, (0, common_1.Param)('id')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, String]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CustomerNotificationsController.prototype, "markRead", null);
|
||||
exports.CustomerNotificationsController = CustomerNotificationsController = __decorate([
|
||||
(0, common_1.Controller)('notifications'),
|
||||
(0, auth_decorators_1.AuthType)('customer'),
|
||||
__metadata("design:paramtypes", [notifications_service_1.NotificationsService])
|
||||
], CustomerNotificationsController);
|
||||
let AdminNotificationsController = class AdminNotificationsController {
|
||||
notifications;
|
||||
constructor(notifications) {
|
||||
this.notifications = notifications;
|
||||
}
|
||||
markAllRead() {
|
||||
return this.notifications.markAllAdminRead();
|
||||
}
|
||||
markRead(id) {
|
||||
return this.notifications.markAdminRead(id);
|
||||
}
|
||||
};
|
||||
exports.AdminNotificationsController = AdminNotificationsController;
|
||||
__decorate([
|
||||
(0, common_1.Patch)('read-all'),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", void 0)
|
||||
], AdminNotificationsController.prototype, "markAllRead", null);
|
||||
__decorate([
|
||||
(0, common_1.Patch)(':id/read'),
|
||||
__param(0, (0, common_1.Param)('id')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], AdminNotificationsController.prototype, "markRead", null);
|
||||
exports.AdminNotificationsController = AdminNotificationsController = __decorate([
|
||||
(0, common_1.Controller)('admin/notifications'),
|
||||
(0, auth_decorators_1.AuthType)('admin'),
|
||||
__metadata("design:paramtypes", [notifications_service_1.NotificationsService])
|
||||
], AdminNotificationsController);
|
||||
//# sourceMappingURL=notifications.controller.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"notifications.controller.js","sourceRoot":"","sources":["../../src/notifications/notifications.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA+D;AAC/D,mEAA+D;AAC/D,0EAAgE;AAChE,wFAA0E;AAKnE,IAAM,+BAA+B,GAArC,MAAM,+BAA+B;IACb;IAA7B,YAA6B,aAAmC;QAAnC,kBAAa,GAAb,aAAa,CAAsB;IAAG,CAAC;IAGpE,IAAI,CAAgB,IAAmB;QACrC,OAAO,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC;IAGD,WAAW,CAAgB,IAAmB;QAC5C,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjD,CAAC;IAGD,WAAW,CAAgB,IAAmB;QAC5C,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjD,CAAC;IAGD,QAAQ,CAAgB,IAAmB,EAAe,EAAU;QAClE,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAClD,CAAC;CACF,CAAA;AAtBY,0EAA+B;AAI1C;IADC,IAAA,YAAG,GAAE;IACA,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;2DAElB;AAGD;IADC,IAAA,YAAG,EAAC,cAAc,CAAC;IACP,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;kEAEzB;AAGD;IADC,IAAA,cAAK,EAAC,UAAU,CAAC;IACL,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;kEAEzB;AAGD;IADC,IAAA,cAAK,EAAC,UAAU,CAAC;IACR,WAAA,IAAA,oCAAW,GAAE,CAAA;IAAuB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;+DAExD;0CArBU,+BAA+B;IAF3C,IAAA,mBAAU,EAAC,eAAe,CAAC;IAC3B,IAAA,0BAAQ,EAAC,UAAU,CAAC;qCAEyB,4CAAoB;GADrD,+BAA+B,CAsB3C;AAIM,IAAM,4BAA4B,GAAlC,MAAM,4BAA4B;IACV;IAA7B,YAA6B,aAAmC;QAAnC,kBAAa,GAAb,aAAa,CAAsB;IAAG,CAAC;IAGpE,WAAW;QACT,OAAO,IAAI,CAAC,aAAa,CAAC,gBAAgB,EAAE,CAAC;IAC/C,CAAC;IAGD,QAAQ,CAAc,EAAU;QAC9B,OAAO,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;CACF,CAAA;AAZY,oEAA4B;AAIvC;IADC,IAAA,cAAK,EAAC,UAAU,CAAC;;;;+DAGjB;AAGD;IADC,IAAA,cAAK,EAAC,UAAU,CAAC;IACR,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;4DAEpB;uCAXU,4BAA4B;IAFxC,IAAA,mBAAU,EAAC,qBAAqB,CAAC;IACjC,IAAA,0BAAQ,EAAC,OAAO,CAAC;qCAE4B,4CAAoB;GADrD,4BAA4B,CAYxC"}
|
||||
@@ -0,0 +1,2 @@
|
||||
export declare class NotificationsModule {
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NotificationsModule = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const notifications_service_1 = require("./notifications.service");
|
||||
const notifications_controller_1 = require("./notifications.controller");
|
||||
const sms_module_1 = require("../sms/sms.module");
|
||||
let NotificationsModule = class NotificationsModule {
|
||||
};
|
||||
exports.NotificationsModule = NotificationsModule;
|
||||
exports.NotificationsModule = NotificationsModule = __decorate([
|
||||
(0, common_1.Module)({
|
||||
imports: [sms_module_1.SmsModule],
|
||||
controllers: [notifications_controller_1.CustomerNotificationsController, notifications_controller_1.AdminNotificationsController],
|
||||
providers: [notifications_service_1.NotificationsService],
|
||||
exports: [notifications_service_1.NotificationsService],
|
||||
})
|
||||
], NotificationsModule);
|
||||
//# sourceMappingURL=notifications.module.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"notifications.module.js","sourceRoot":"","sources":["../../src/notifications/notifications.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,mEAA+D;AAC/D,yEAGoC;AACpC,kDAA8C;AAQvC,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;CAAG,CAAA;AAAtB,kDAAmB;8BAAnB,mBAAmB;IAN/B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,sBAAS,CAAC;QACpB,WAAW,EAAE,CAAC,0DAA+B,EAAE,uDAA4B,CAAC;QAC5E,SAAS,EAAE,CAAC,4CAAoB,CAAC;QACjC,OAAO,EAAE,CAAC,4CAAoB,CAAC;KAChC,CAAC;GACW,mBAAmB,CAAG"}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { NotificationTone } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { SmsService } from '../sms/sms.service';
|
||||
import { type CustomerEvent } from './notification-events';
|
||||
type NotifyOptions = {
|
||||
amount?: number | null;
|
||||
note?: string | null;
|
||||
carrier?: string | null;
|
||||
trackingNumber?: string | null;
|
||||
};
|
||||
export declare class NotificationsService {
|
||||
private readonly prisma;
|
||||
private readonly sms;
|
||||
private readonly config;
|
||||
private readonly logger;
|
||||
constructor(prisma: PrismaService, sms: SmsService, config: ConfigService);
|
||||
notifyOrderEvent(orderId: string, event: CustomerEvent, options?: NotifyOptions): Promise<void>;
|
||||
dispatchOrderEvent(orderId: string, event: CustomerEvent, options?: NotifyOptions): void;
|
||||
notifyAdmin(input: {
|
||||
title: string;
|
||||
body: string;
|
||||
tone?: NotificationTone;
|
||||
link?: string;
|
||||
}): Promise<void>;
|
||||
dispatchAdmin(input: {
|
||||
title: string;
|
||||
body: string;
|
||||
tone?: NotificationTone;
|
||||
link?: string;
|
||||
}): void;
|
||||
listForCustomer(customerId: string, limit?: number): Promise<{
|
||||
id: string;
|
||||
orderId: string | undefined;
|
||||
event: string;
|
||||
title: string;
|
||||
body: string;
|
||||
tone: import("@prisma/client").$Enums.NotificationTone;
|
||||
link: string | undefined;
|
||||
isRead: boolean;
|
||||
sentAt: string;
|
||||
}[]>;
|
||||
unreadCount(customerId: string): Promise<{
|
||||
count: number;
|
||||
}>;
|
||||
markRead(customerId: string, id: string): Promise<{
|
||||
count: number;
|
||||
}>;
|
||||
markAllRead(customerId: string): Promise<{
|
||||
count: number;
|
||||
}>;
|
||||
markAdminRead(id: string): Promise<{
|
||||
ok: boolean;
|
||||
}>;
|
||||
markAllAdminRead(): Promise<{
|
||||
ok: boolean;
|
||||
}>;
|
||||
private appName;
|
||||
private isSmsEventEnabled;
|
||||
}
|
||||
export {};
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
var NotificationsService_1;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NotificationsService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const config_1 = require("@nestjs/config");
|
||||
const client_1 = require("@prisma/client");
|
||||
const prisma_service_1 = require("../prisma/prisma.service");
|
||||
const sms_service_1 = require("../sms/sms.service");
|
||||
const notification_events_1 = require("./notification-events");
|
||||
let NotificationsService = NotificationsService_1 = class NotificationsService {
|
||||
prisma;
|
||||
sms;
|
||||
config;
|
||||
logger = new common_1.Logger(NotificationsService_1.name);
|
||||
constructor(prisma, sms, config) {
|
||||
this.prisma = prisma;
|
||||
this.sms = sms;
|
||||
this.config = config;
|
||||
}
|
||||
async notifyOrderEvent(orderId, event, options = {}) {
|
||||
try {
|
||||
const order = await this.prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
select: {
|
||||
id: true,
|
||||
orderNumber: true,
|
||||
userId: true,
|
||||
finalAmount: true,
|
||||
totalAmount: true,
|
||||
user: { select: { phone: true } },
|
||||
},
|
||||
});
|
||||
if (!order)
|
||||
return;
|
||||
const template = notification_events_1.CUSTOMER_EVENTS[event];
|
||||
const ctx = {
|
||||
orderNumber: order.orderNumber,
|
||||
appName: this.appName(),
|
||||
amount: options.amount ?? order.finalAmount ?? order.totalAmount ?? undefined,
|
||||
note: options.note ?? null,
|
||||
carrier: options.carrier ?? null,
|
||||
trackingNumber: options.trackingNumber ?? null,
|
||||
};
|
||||
const notification = await this.prisma.customerNotification.create({
|
||||
data: {
|
||||
customerId: order.userId,
|
||||
orderId: order.id,
|
||||
event,
|
||||
title: template.title,
|
||||
body: template.body(ctx),
|
||||
tone: template.tone,
|
||||
link: `/orders/${order.id}`,
|
||||
},
|
||||
});
|
||||
if (!template.sms || !template.smsSwitch)
|
||||
return;
|
||||
const enabled = await this.isSmsEventEnabled(template.smsSwitch);
|
||||
if (!enabled || !this.sms.isEnabled())
|
||||
return;
|
||||
const result = await this.sms.send(order.user.phone, template.sms(ctx));
|
||||
if (result.success) {
|
||||
await this.prisma.customerNotification.update({
|
||||
where: { id: notification.id },
|
||||
data: { smsSent: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.error(`notifyOrderEvent(${event}) failed for order ${orderId}`, err instanceof Error ? err.stack : err);
|
||||
}
|
||||
}
|
||||
dispatchOrderEvent(orderId, event, options = {}) {
|
||||
void this.notifyOrderEvent(orderId, event, options);
|
||||
}
|
||||
async notifyAdmin(input) {
|
||||
try {
|
||||
await this.prisma.adminNotification.create({
|
||||
data: {
|
||||
title: input.title,
|
||||
body: input.body,
|
||||
tone: input.tone ?? client_1.NotificationTone.info,
|
||||
link: input.link ?? null,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.error('notifyAdmin failed', err instanceof Error ? err.stack : err);
|
||||
}
|
||||
}
|
||||
dispatchAdmin(input) {
|
||||
void this.notifyAdmin(input);
|
||||
}
|
||||
async listForCustomer(customerId, limit = 50) {
|
||||
const rows = await this.prisma.customerNotification.findMany({
|
||||
where: { customerId },
|
||||
orderBy: { sentAt: 'desc' },
|
||||
take: limit,
|
||||
});
|
||||
return rows.map((n) => ({
|
||||
id: n.id,
|
||||
orderId: n.orderId ?? undefined,
|
||||
event: n.event,
|
||||
title: n.title,
|
||||
body: n.body,
|
||||
tone: n.tone ?? client_1.NotificationTone.info,
|
||||
link: n.link ?? undefined,
|
||||
isRead: n.isRead,
|
||||
sentAt: n.sentAt.toISOString(),
|
||||
}));
|
||||
}
|
||||
async unreadCount(customerId) {
|
||||
const count = await this.prisma.customerNotification.count({
|
||||
where: { customerId, isRead: false },
|
||||
});
|
||||
return { count };
|
||||
}
|
||||
async markRead(customerId, id) {
|
||||
const existing = await this.prisma.customerNotification.findFirst({
|
||||
where: { id, customerId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!existing)
|
||||
throw new common_1.NotFoundException('اعلان یافت نشد');
|
||||
await this.prisma.customerNotification.update({
|
||||
where: { id },
|
||||
data: { isRead: true },
|
||||
});
|
||||
return this.unreadCount(customerId);
|
||||
}
|
||||
async markAllRead(customerId) {
|
||||
await this.prisma.customerNotification.updateMany({
|
||||
where: { customerId, isRead: false },
|
||||
data: { isRead: true },
|
||||
});
|
||||
return { count: 0 };
|
||||
}
|
||||
async markAdminRead(id) {
|
||||
await this.prisma.adminNotification.updateMany({
|
||||
where: { id },
|
||||
data: { isRead: true },
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
async markAllAdminRead() {
|
||||
await this.prisma.adminNotification.updateMany({
|
||||
where: { isRead: false },
|
||||
data: { isRead: true },
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
appName() {
|
||||
return this.config.get('SMS_APP_NAME', 'پارچه') ?? 'پارچه';
|
||||
}
|
||||
async isSmsEventEnabled(key) {
|
||||
const settings = await this.prisma.systemSettings.findUnique({
|
||||
where: { id: 'default' },
|
||||
select: {
|
||||
smsOrderRegistered: true,
|
||||
smsPricingReady: true,
|
||||
smsPaymentReminder: true,
|
||||
smsCheckReview: true,
|
||||
smsShippingUpdate: true,
|
||||
},
|
||||
});
|
||||
return settings ? settings[key] : true;
|
||||
}
|
||||
};
|
||||
exports.NotificationsService = NotificationsService;
|
||||
exports.NotificationsService = NotificationsService = NotificationsService_1 = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
|
||||
sms_service_1.SmsService,
|
||||
config_1.ConfigService])
|
||||
], NotificationsService);
|
||||
//# sourceMappingURL=notifications.service.js.map
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user