chore: add notification service for all user action
This commit is contained in:
Binary file not shown.
@@ -323,8 +323,30 @@ export const enum NotificationMessage {
|
|||||||
USERID_IS_UUID = "شناسه کاربر باید یک UUID معتبر باشد",
|
USERID_IS_UUID = "شناسه کاربر باید یک UUID معتبر باشد",
|
||||||
USERID_IS_STRING = "شناسه کاربر باید یک رشته متنی باشد",
|
USERID_IS_STRING = "شناسه کاربر باید یک رشته متنی باشد",
|
||||||
LOGIN = "لاگین",
|
LOGIN = "لاگین",
|
||||||
LOGIN_MESSAGE = "یک لاگین به حساب کاربری شما صورت گرفت",
|
LOGIN_MESSAGE = "یک لاگین در تاریخ [loginDate] به حساب کاربری شما صورت گرفت",
|
||||||
NOTIFICATION_NOT_FOUND_OR_ACCESS_DENIED = "اعلان یافت نشد یا دسترسی غیرمجاز است",
|
NOTIFICATION_NOT_FOUND_OR_ACCESS_DENIED = "اعلان یافت نشد یا دسترسی غیرمجاز است",
|
||||||
|
INVOICE_CREATION = "صدور فاکتور",
|
||||||
|
INVOICE_CREATION_MESSAGE = "یک فاکتور به شماره [id] جدید برای شما صادر شده است",
|
||||||
|
ANNOUNCEMENT = "اطلاعیه",
|
||||||
|
ANNOUNCEMENT_MESSAGE = "یک اطلاعیه جدید برای شما ارسال شده است",
|
||||||
|
WALLET_CHARGE = "شارژ کیف پول",
|
||||||
|
WALLET_CHARGE_MESSAGE = "کیف پول شما به مبلغ [amount] تومان شارژ شد",
|
||||||
|
WALLET_DEDUCTION = "کسر از کیف پول",
|
||||||
|
WALLET_DEDUCTION_MESSAGE = "مبلغ [amount] تومان از کیف پول شما کسر شد",
|
||||||
|
BILL_INVOICE_REMINDER = "یادآوری پرداخت",
|
||||||
|
BILL_INVOICE_REMINDER_MESSAGE = "فاکتور شماره [invoiceNumber] هنوز پرداخت نشده است",
|
||||||
|
BILL_INVOICE = "فاکتور پرداختی",
|
||||||
|
BILL_INVOICE_MESSAGE = "فاکتور شماره [invoiceNumber] با موفقیت پرداخت شد",
|
||||||
|
CREATE_SERVICE = "ایجاد سرویس",
|
||||||
|
CREATE_SERVICE_MESSAGE = "سرویس جدیدی با عنوان [serviceName] ایجاد شد",
|
||||||
|
UNBLOCK_SERVICE = "فعالسازی سرویس",
|
||||||
|
UNBLOCK_SERVICE_MESSAGE = "سرویس [serviceName] مجددا فعال شده است",
|
||||||
|
BLOCK_SERVICE = "غیرفعالسازی سرویس",
|
||||||
|
BLOCK_SERVICE_MESSAGE = "سرویس [serviceName] غیرفعال شده است",
|
||||||
|
ANSWER_TICKET = "پاسخ تیکت",
|
||||||
|
ANSWER_TICKET_MESSAGE = "پاسخ جدیدی برای تیکت [ticketId] دریافت کردید",
|
||||||
|
CREATE_TICKET = "ایجاد تیکت",
|
||||||
|
CREATE_TICKET_MESSAGE = "تیکت شما با شماره [ticketNumber] ثبت شد",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum SubscriptionMessage {
|
export const enum SubscriptionMessage {
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ export function smsConfigs() {
|
|||||||
API_KEY: configService.getOrThrow<string>("SMS_API_KEY"),
|
API_KEY: configService.getOrThrow<string>("SMS_API_KEY"),
|
||||||
SMS_PATTERN_OTP: configService.getOrThrow<string>("SMS_PATTERN_OTP"),
|
SMS_PATTERN_OTP: configService.getOrThrow<string>("SMS_PATTERN_OTP"),
|
||||||
SMS_PATTERN_INVOICE: configService.getOrThrow<string>("SMS_PATTERN_INVOICE"),
|
SMS_PATTERN_INVOICE: configService.getOrThrow<string>("SMS_PATTERN_INVOICE"),
|
||||||
|
SMS_PATTERN_LOGIN: configService.getOrThrow<string>("SMS_PATTERN_LOGIN"),
|
||||||
|
SMS_PATTERN_INVOICE_CREATED: configService.getOrThrow<string>("SMS_PATTERN_INVOICE_CREATED"),
|
||||||
|
SMS_PATTERN_ANNOUNCEMENT: configService.getOrThrow<string>("SMS_PATTERN_ANNOUNCEMENT"),
|
||||||
|
SMS_PATTERN_WALLET_CHARGE: configService.getOrThrow<string>("SMS_PATTERN_WALLET_CHARGE"),
|
||||||
|
SMS_PATTERN_WALLET_DEDUCTION: configService.getOrThrow<string>("SMS_PATTERN_WALLET_DEDUCTION"),
|
||||||
|
SMS_PATTERN_TICKET_CREATED: configService.getOrThrow<string>("SMS_PATTERN_TICKET_CREATED"),
|
||||||
|
SMS_PATTERN_TICKET_ANSWERED: configService.getOrThrow<string>("SMS_PATTERN_WALLET_DEDUCTION"),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -19,4 +26,11 @@ export interface ISmsConfigs {
|
|||||||
API_KEY: string;
|
API_KEY: string;
|
||||||
SMS_PATTERN_OTP: string;
|
SMS_PATTERN_OTP: string;
|
||||||
SMS_PATTERN_INVOICE: string;
|
SMS_PATTERN_INVOICE: string;
|
||||||
|
SMS_PATTERN_LOGIN: string;
|
||||||
|
SMS_PATTERN_INVOICE_CREATED: string;
|
||||||
|
SMS_PATTERN_ANNOUNCEMENT: string;
|
||||||
|
SMS_PATTERN_WALLET_CHARGE: string;
|
||||||
|
SMS_PATTERN_WALLET_DEDUCTION: string;
|
||||||
|
SMS_PATTERN_TICKET_CREATED: string;
|
||||||
|
SMS_PATTERN_TICKET_ANSWERED: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export class AuthService {
|
|||||||
|
|
||||||
const tokens = this.generateAccessAndRefreshToken(user);
|
const tokens = this.generateAccessAndRefreshToken(user);
|
||||||
|
|
||||||
await this.notificationService.createLoginNotification(user.id);
|
await this.notificationService.createLoginNotification(user.id, { userPhone: user.phone });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: AuthMessage.PASSWORD_LOGIN_SUCCESS,
|
message: AuthMessage.PASSWORD_LOGIN_SUCCESS,
|
||||||
@@ -176,7 +176,7 @@ export class AuthService {
|
|||||||
|
|
||||||
const tokens = this.generateAccessAndRefreshToken(user);
|
const tokens = this.generateAccessAndRefreshToken(user);
|
||||||
|
|
||||||
await this.notificationService.createLoginNotification(user.id);
|
await this.notificationService.createLoginNotification(user.id, { userPhone: user.phone });
|
||||||
return {
|
return {
|
||||||
message: AuthMessage.LOGIN_SUCCESS,
|
message: AuthMessage.LOGIN_SUCCESS,
|
||||||
...tokens,
|
...tokens,
|
||||||
|
|||||||
@@ -10,12 +10,19 @@ import { Discount } from "../discounts/entities/discount.entity";
|
|||||||
import { UsageDiscount } from "../discounts/entities/usage-discount.entity";
|
import { UsageDiscount } from "../discounts/entities/usage-discount.entity";
|
||||||
import { DiscountRepository } from "../discounts/repositories/discount.repository";
|
import { DiscountRepository } from "../discounts/repositories/discount.repository";
|
||||||
import { UsageDiscountRepository } from "../discounts/repositories/usage-discount.repository";
|
import { UsageDiscountRepository } from "../discounts/repositories/usage-discount.repository";
|
||||||
|
import { NotificationModule } from "../notifications/notifications.module";
|
||||||
import { UsersModule } from "../users/users.module";
|
import { UsersModule } from "../users/users.module";
|
||||||
import { UtilsModule } from "../utils/utils.module";
|
import { UtilsModule } from "../utils/utils.module";
|
||||||
import { WalletsModule } from "../wallets/wallets.module";
|
import { WalletsModule } from "../wallets/wallets.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Invoice, InvoiceItem, Discount, UsageDiscount]), UsersModule, WalletsModule, UtilsModule],
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([Invoice, InvoiceItem, Discount, UsageDiscount]),
|
||||||
|
UsersModule,
|
||||||
|
WalletsModule,
|
||||||
|
UtilsModule,
|
||||||
|
NotificationModule,
|
||||||
|
],
|
||||||
providers: [InvoicesService, InvoicesRepository, DiscountRepository, UsageDiscountRepository],
|
providers: [InvoicesService, InvoicesRepository, DiscountRepository, UsageDiscountRepository],
|
||||||
controllers: [InvoicesController],
|
controllers: [InvoicesController],
|
||||||
exports: [InvoicesService],
|
exports: [InvoicesService],
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { AuthMessage, DiscountMessage, InvoiceMessage, WalletMessage } from "../
|
|||||||
import { VerifyOtpWithUserId } from "../../auth/DTO/verify-otp.dto";
|
import { VerifyOtpWithUserId } from "../../auth/DTO/verify-otp.dto";
|
||||||
import { DiscountRepository } from "../../discounts/repositories/discount.repository";
|
import { DiscountRepository } from "../../discounts/repositories/discount.repository";
|
||||||
import { UsageDiscountRepository } from "../../discounts/repositories/usage-discount.repository";
|
import { UsageDiscountRepository } from "../../discounts/repositories/usage-discount.repository";
|
||||||
|
import { NotificationsService } from "../../notifications/providers/notifications.service";
|
||||||
import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity";
|
import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity";
|
||||||
import { UsersService } from "../../users/providers/users.service";
|
import { UsersService } from "../../users/providers/users.service";
|
||||||
import { OTPService } from "../../utils/providers/otp.service";
|
import { OTPService } from "../../utils/providers/otp.service";
|
||||||
@@ -28,6 +29,7 @@ export class InvoicesService {
|
|||||||
private readonly logger = new Logger(InvoicesService.name);
|
private readonly logger = new Logger(InvoicesService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private readonly notificationsService: NotificationsService,
|
||||||
private readonly invoiceRepository: InvoicesRepository,
|
private readonly invoiceRepository: InvoicesRepository,
|
||||||
private readonly discountRepository: DiscountRepository,
|
private readonly discountRepository: DiscountRepository,
|
||||||
private readonly usageDiscountRepository: UsageDiscountRepository,
|
private readonly usageDiscountRepository: UsageDiscountRepository,
|
||||||
@@ -41,38 +43,63 @@ export class InvoicesService {
|
|||||||
///********************************** */
|
///********************************** */
|
||||||
|
|
||||||
async createInvoiceAdmin(createDto: CreateInvoiceDto) {
|
async createInvoiceAdmin(createDto: CreateInvoiceDto) {
|
||||||
//
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
const invoiceItems = createDto.items.map((item) => {
|
|
||||||
return {
|
await queryRunner.connect();
|
||||||
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const invoiceItems = createDto.items.map((item) => ({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
count: item.count,
|
count: item.count,
|
||||||
unitPrice: item.unitPrice,
|
unitPrice: item.unitPrice,
|
||||||
discount: item.discount || 0,
|
discount: item.discount || 0,
|
||||||
totalPrice: item.unitPrice * item.count - (item.unitPrice * item.count * item.discount) / 100,
|
totalPrice: item.unitPrice * item.count - (item.unitPrice * item.count * item.discount) / 100,
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
//
|
|
||||||
const totalPrice = invoiceItems.reduce((sum, item) => new Decimal(item.totalPrice).add(sum), new Decimal(0));
|
|
||||||
const tax = totalPrice.mul(0.1);
|
|
||||||
//
|
|
||||||
const dueDate = new Date();
|
|
||||||
dueDate.setDate(dueDate.getDate() + 5);
|
|
||||||
//
|
|
||||||
const invoice = this.invoiceRepository.create({
|
|
||||||
user: { id: createDto.userId },
|
|
||||||
totalPrice: totalPrice.toNumber(),
|
|
||||||
items: invoiceItems,
|
|
||||||
tax: tax.toNumber(),
|
|
||||||
dueDate,
|
|
||||||
});
|
|
||||||
//
|
|
||||||
await this.invoiceRepository.save(invoice);
|
|
||||||
//TODO: notify user
|
|
||||||
|
|
||||||
return {
|
const totalPrice = invoiceItems.reduce((sum, item) => new Decimal(item.totalPrice).add(sum), new Decimal(0));
|
||||||
message: InvoiceMessage.CREATED,
|
const tax = totalPrice.mul(0.1);
|
||||||
invoice,
|
|
||||||
};
|
const dueDate = new Date();
|
||||||
|
dueDate.setDate(dueDate.getDate() + 5);
|
||||||
|
|
||||||
|
const invoice = queryRunner.manager.create(Invoice, {
|
||||||
|
user: { id: createDto.userId },
|
||||||
|
totalPrice: totalPrice.toNumber(),
|
||||||
|
items: invoiceItems,
|
||||||
|
tax: tax.toNumber(),
|
||||||
|
dueDate,
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryRunner.manager.save(Invoice, invoice);
|
||||||
|
|
||||||
|
const user = await this.usersService.findOneByIdWithQueryRunner(createDto.userId, queryRunner);
|
||||||
|
|
||||||
|
await this.notificationsService.createInvoiceCreationNotification(
|
||||||
|
createDto.userId,
|
||||||
|
{
|
||||||
|
invoiceId: invoice.numericId.toString(),
|
||||||
|
dueDate: invoice.dueDate,
|
||||||
|
price: new Decimal(invoice.totalPrice).toNumber(),
|
||||||
|
userPhone: user.phone,
|
||||||
|
userEmail: user.email,
|
||||||
|
items: invoiceItems.map((item) => item.name).join(", "),
|
||||||
|
},
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.commitTransaction();
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: InvoiceMessage.CREATED,
|
||||||
|
invoice,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await queryRunner.rollbackTransaction();
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//********************************** */
|
//********************************** */
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { Type } from "class-transformer";
|
import { Type } from "class-transformer";
|
||||||
import { IsOptional } from "class-validator";
|
import { IsIn, IsOptional } from "class-validator";
|
||||||
|
|
||||||
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||||
|
|
||||||
export class SearchNotificationQueryDto extends PaginationDto {
|
export class SearchNotificationQueryDto extends PaginationDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
|
@IsIn([0, 1])
|
||||||
@ApiPropertyOptional({ description: "Notification status", example: 1 })
|
@ApiPropertyOptional({ description: "Notification status", example: 1 })
|
||||||
isRead?: number;
|
isRead?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export enum NotificationType {
|
export enum NotificationType {
|
||||||
LOGIN = "LOGIN",
|
LOGIN = "LOGIN",
|
||||||
PASSWORD_CAHNGED = "PASSWORD_CHANGED",
|
PASSWORD_CHANGED = "PASSWORD_CHANGED",
|
||||||
WALLET_CHARGED = "WALLER_CHARGED",
|
WALLET_CHARGED = "WALLER_CHARGED",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
export interface IBaseNotificationData {
|
||||||
|
userPhone: string;
|
||||||
|
userEmail?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInvoiceNotificationData extends IBaseNotificationData {
|
||||||
|
price: number;
|
||||||
|
items: string;
|
||||||
|
dueDate: Date;
|
||||||
|
invoiceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IServiceNotificationData extends IBaseNotificationData {
|
||||||
|
serviceName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITicketNotificationData extends IBaseNotificationData {
|
||||||
|
ticketId: string;
|
||||||
|
subject: string;
|
||||||
|
date: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWalletNotificationData extends IBaseNotificationData {
|
||||||
|
amount: number;
|
||||||
|
balance: number;
|
||||||
|
date: Date;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGenericNotificationData extends IBaseNotificationData {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAnnouncementNotificationData extends IBaseNotificationData {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
date: Date;
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import { ParamDto } from "../../common/DTO/param.dto";
|
|||||||
|
|
||||||
@Controller("notifications")
|
@Controller("notifications")
|
||||||
@ApiTags("Notifications")
|
@ApiTags("Notifications")
|
||||||
|
@AuthGuards()
|
||||||
export class NotificationController {
|
export class NotificationController {
|
||||||
constructor(private readonly notificationService: NotificationsService) {}
|
constructor(private readonly notificationService: NotificationsService) {}
|
||||||
|
|
||||||
@@ -17,7 +18,6 @@ export class NotificationController {
|
|||||||
|
|
||||||
@ApiOperation({ summary: "all notifications by user" })
|
@ApiOperation({ summary: "all notifications by user" })
|
||||||
@Pagination()
|
@Pagination()
|
||||||
@AuthGuards()
|
|
||||||
@Get()
|
@Get()
|
||||||
getAllNotifications(@UserDec("id") userId: string, @Query() queryDto: SearchNotificationQueryDto) {
|
getAllNotifications(@UserDec("id") userId: string, @Query() queryDto: SearchNotificationQueryDto) {
|
||||||
return this.notificationService.getAllNotifications(queryDto, userId);
|
return this.notificationService.getAllNotifications(queryDto, userId);
|
||||||
@@ -26,7 +26,6 @@ export class NotificationController {
|
|||||||
//********************* */
|
//********************* */
|
||||||
|
|
||||||
@ApiOperation({ summary: "mark user notification as read" })
|
@ApiOperation({ summary: "mark user notification as read" })
|
||||||
@AuthGuards()
|
|
||||||
@Patch(":id/read")
|
@Patch(":id/read")
|
||||||
markAsRead(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
markAsRead(@Param() paramDto: ParamDto, @UserDec("id") userId: string) {
|
||||||
return this.notificationService.markAsRead(paramDto.id, userId);
|
return this.notificationService.markAsRead(paramDto.id, userId);
|
||||||
@@ -35,7 +34,6 @@ export class NotificationController {
|
|||||||
//********************* */
|
//********************* */
|
||||||
|
|
||||||
@ApiOperation({ summary: "mark user all notification as read" })
|
@ApiOperation({ summary: "mark user all notification as read" })
|
||||||
@AuthGuards()
|
|
||||||
@Patch("read-all")
|
@Patch("read-all")
|
||||||
markAllAsRead(@UserDec("id") userId: string) {
|
markAllAsRead(@UserDec("id") userId: string) {
|
||||||
return this.notificationService.markAllAsRead(userId);
|
return this.notificationService.markAllAsRead(userId);
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import { Notification } from "./entities/notification.entity";
|
|||||||
import { NotificationController } from "./notifications.controller";
|
import { NotificationController } from "./notifications.controller";
|
||||||
import { NotificationsService } from "./providers/notifications.service";
|
import { NotificationsService } from "./providers/notifications.service";
|
||||||
import { NotificationRepository } from "./repositories/notifications.repository";
|
import { NotificationRepository } from "./repositories/notifications.repository";
|
||||||
import { User } from "../users/entities/user.entity";
|
import { SettingModule } from "../settings/settings.module";
|
||||||
import { UsersModule } from "../users/users.module";
|
import { UtilsModule } from "../utils/utils.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Notification, User]), UsersModule],
|
imports: [TypeOrmModule.forFeature([Notification]), UtilsModule, SettingModule],
|
||||||
providers: [NotificationRepository, NotificationsService],
|
providers: [NotificationRepository, NotificationsService],
|
||||||
controllers: [NotificationController],
|
controllers: [NotificationController],
|
||||||
exports: [NotificationRepository, NotificationsService],
|
exports: [NotificationRepository, NotificationsService],
|
||||||
|
|||||||
@@ -1,30 +1,73 @@
|
|||||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||||
|
import { InjectDataSource } from "@nestjs/typeorm";
|
||||||
|
import { DataSource, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
import { CommonMessage, NotificationMessage, UserMessage } from "../../../common/enums/message.enum";
|
import { CommonMessage, NotificationMessage } from "../../../common/enums/message.enum";
|
||||||
import { UsersService } from "../../users/providers/users.service";
|
import { NotifType } from "../../settings/enums/notif-settings.enum";
|
||||||
|
import { UserSettingsService } from "../../settings/providers/user-settings.service";
|
||||||
|
import { dateFormat, numberFormat } from "../../utils/providers/international.utils";
|
||||||
|
import { SmsService } from "../../utils/providers/sms.service";
|
||||||
import { CreateNotificationDto } from "../DTO/create-notification.dto";
|
import { CreateNotificationDto } from "../DTO/create-notification.dto";
|
||||||
import { SearchNotificationQueryDto } from "../DTO/search-notification-query.dto";
|
import { SearchNotificationQueryDto } from "../DTO/search-notification-query.dto";
|
||||||
|
import { Notification } from "../entities/notification.entity";
|
||||||
|
import {
|
||||||
|
IAnnouncementNotificationData,
|
||||||
|
IBaseNotificationData,
|
||||||
|
IInvoiceNotificationData,
|
||||||
|
ITicketNotificationData,
|
||||||
|
IWalletNotificationData,
|
||||||
|
} from "../interfaces/ISendNotificationData";
|
||||||
import { NotificationRepository } from "../repositories/notifications.repository";
|
import { NotificationRepository } from "../repositories/notifications.repository";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NotificationsService {
|
export class NotificationsService {
|
||||||
|
private readonly logger = new Logger(NotificationsService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly notificationRepository: NotificationRepository,
|
private readonly notificationRepository: NotificationRepository,
|
||||||
private readonly usersService: UsersService,
|
private readonly smsService: SmsService,
|
||||||
|
private readonly userSettingsService: UserSettingsService,
|
||||||
|
@InjectDataSource() private readonly dataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
//************************ */
|
//************************ */
|
||||||
|
|
||||||
async createNotification(createNotificationDto: CreateNotificationDto) {
|
async createNotification(createNotificationDto: CreateNotificationDto, queryRunner?: QueryRunner) {
|
||||||
const { user } = await this.usersService.findOneById(createNotificationDto.recipientId);
|
if (!queryRunner) {
|
||||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
const localQueryRunner = this.dataSource.createQueryRunner();
|
||||||
const notification = this.notificationRepository.create({
|
|
||||||
...createNotificationDto,
|
|
||||||
recipient: user,
|
|
||||||
});
|
|
||||||
await this.notificationRepository.save(notification);
|
|
||||||
|
|
||||||
return { notification };
|
await localQueryRunner.connect();
|
||||||
|
await localQueryRunner.startTransaction();
|
||||||
|
try {
|
||||||
|
//
|
||||||
|
const notification = localQueryRunner.manager.create(Notification, {
|
||||||
|
...createNotificationDto,
|
||||||
|
recipient: { id: createNotificationDto.recipientId },
|
||||||
|
});
|
||||||
|
await localQueryRunner.manager.save(notification);
|
||||||
|
|
||||||
|
await localQueryRunner.commitTransaction();
|
||||||
|
|
||||||
|
return notification;
|
||||||
|
//
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("error in createNotification", error);
|
||||||
|
await localQueryRunner.rollbackTransaction();
|
||||||
|
throw error;
|
||||||
|
//
|
||||||
|
} finally {
|
||||||
|
await localQueryRunner.release();
|
||||||
|
}
|
||||||
|
//
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
const notification = queryRunner.manager.create(Notification, {
|
||||||
|
...createNotificationDto,
|
||||||
|
recipient: { id: createNotificationDto.recipientId },
|
||||||
|
});
|
||||||
|
//
|
||||||
|
await queryRunner.manager.save(notification);
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************ */
|
//************************ */
|
||||||
@@ -42,20 +85,13 @@ export class NotificationsService {
|
|||||||
|
|
||||||
//************************ */
|
//************************ */
|
||||||
|
|
||||||
async createLoginNotification(recipientId: string) {
|
|
||||||
const message = NotificationMessage.LOGIN_MESSAGE;
|
|
||||||
return this.createNotification({ title: NotificationMessage.LOGIN, message, recipientId });
|
|
||||||
}
|
|
||||||
|
|
||||||
//************************ */
|
|
||||||
|
|
||||||
async getAllNotifications(queryDto: SearchNotificationQueryDto, recipientId: string) {
|
async getAllNotifications(queryDto: SearchNotificationQueryDto, recipientId: string) {
|
||||||
return await this.notificationRepository.getAllNotifications(queryDto, recipientId);
|
return await this.notificationRepository.getAllNotifications(queryDto, recipientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************ */
|
//************************ */
|
||||||
|
|
||||||
async getOneNotification(id: string) {
|
async getNotificationById(id: string) {
|
||||||
const notification = await this.notificationRepository.findOneBy({ id });
|
const notification = await this.notificationRepository.findOneBy({ id });
|
||||||
if (!notification) throw new BadRequestException(NotificationMessage.NOTIFICATION_NOT_FOUND_OR_ACCESS_DENIED);
|
if (!notification) throw new BadRequestException(NotificationMessage.NOTIFICATION_NOT_FOUND_OR_ACCESS_DENIED);
|
||||||
return { notification };
|
return { notification };
|
||||||
@@ -64,18 +100,199 @@ export class NotificationsService {
|
|||||||
//************************ */
|
//************************ */
|
||||||
|
|
||||||
async markAllAsRead(userId: string) {
|
async markAllAsRead(userId: string) {
|
||||||
await this.notificationRepository.update(
|
await this.notificationRepository.update({ recipient: { id: userId } }, { isRead: true });
|
||||||
{
|
|
||||||
recipient: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isRead: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
message: CommonMessage.UPDATE_SUCCESS,
|
message: CommonMessage.UPDATE_SUCCESS,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
async createLoginNotification(recipientId: string, sendNotifData: IBaseNotificationData) {
|
||||||
|
const loginDate = new Date().toLocaleString("fa-IR");
|
||||||
|
const message = NotificationMessage.LOGIN_MESSAGE.replace("[loginDate]", loginDate);
|
||||||
|
//sent notification based on the userSettings
|
||||||
|
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.USER_LOGIN, recipientId);
|
||||||
|
if (isActive) {
|
||||||
|
await this.smsService.sendLoginSms(sendNotifData.userPhone, loginDate);
|
||||||
|
//send email too
|
||||||
|
}
|
||||||
|
return this.createNotification({ title: NotificationMessage.LOGIN, message, recipientId });
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
async createInvoiceCreationNotification(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner) {
|
||||||
|
const message = NotificationMessage.INVOICE_CREATION_MESSAGE.replace("[id]", data.invoiceId);
|
||||||
|
//sent notification based on the userSettings
|
||||||
|
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||||
|
NotifType.CREATE_INVOICE,
|
||||||
|
recipientId,
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
|
if (isActive) {
|
||||||
|
await this.smsService.sendInvoiceCreationSms(data.userPhone, data.invoiceId, data.price, data.items, dateFormat(data.dueDate));
|
||||||
|
//send email too
|
||||||
|
}
|
||||||
|
return this.createNotification({ title: NotificationMessage.INVOICE_CREATION, message, recipientId }, queryRunner);
|
||||||
|
}
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
async createAnnouncementNotification(recipientId: string, data: IAnnouncementNotificationData, queryRunner: QueryRunner) {
|
||||||
|
const message = NotificationMessage.ANNOUNCEMENT_MESSAGE;
|
||||||
|
//sent notification based on the userSettings
|
||||||
|
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||||
|
NotifType.ANNOUNCEMENT,
|
||||||
|
recipientId,
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
|
if (isActive) {
|
||||||
|
await this.smsService.sendAnnouncementSms(data.userPhone, data.title, data.description, dateFormat(data.date));
|
||||||
|
//send email too
|
||||||
|
}
|
||||||
|
return this.createNotification({ title: NotificationMessage.ANNOUNCEMENT, message, recipientId });
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
async createWalletChargeNotification(recipientId: string, data: IWalletNotificationData, queryRunner: QueryRunner) {
|
||||||
|
const message = NotificationMessage.WALLET_CHARGE_MESSAGE.replace("[amount]", numberFormat(data.amount));
|
||||||
|
//sent notification based on the userSettings
|
||||||
|
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||||
|
NotifType.WALLET_CHARGE,
|
||||||
|
recipientId,
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
|
if (isActive) {
|
||||||
|
await this.smsService.sendWalletChargeSms(
|
||||||
|
data.userPhone,
|
||||||
|
numberFormat(data.amount),
|
||||||
|
numberFormat(data.balance),
|
||||||
|
dateFormat(data.date),
|
||||||
|
);
|
||||||
|
//send email too
|
||||||
|
}
|
||||||
|
return this.createNotification({ title: NotificationMessage.WALLET_CHARGE, message, recipientId });
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
async createWalletDeductionNotification(recipientId: string, data: IWalletNotificationData, queryRunner: QueryRunner) {
|
||||||
|
const message = NotificationMessage.WALLET_DEDUCTION_MESSAGE.replace("[amount]", numberFormat(data.amount));
|
||||||
|
//sent notification based on the userSettings
|
||||||
|
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||||
|
NotifType.WALLET_DEDUCTION,
|
||||||
|
recipientId,
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
|
if (isActive) {
|
||||||
|
await this.smsService.sendWalletDeductionSms(
|
||||||
|
data.userPhone,
|
||||||
|
numberFormat(data.amount),
|
||||||
|
numberFormat(data.balance),
|
||||||
|
data.reason,
|
||||||
|
dateFormat(data.date),
|
||||||
|
);
|
||||||
|
//send email too
|
||||||
|
}
|
||||||
|
return this.createNotification({ title: NotificationMessage.WALLET_DEDUCTION, message, recipientId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// //************************ */
|
||||||
|
|
||||||
|
async createAnswerTicketNotification(recipientId: string, data: ITicketNotificationData, queryRunner: QueryRunner) {
|
||||||
|
const message = NotificationMessage.ANSWER_TICKET_MESSAGE.replace("[ticketId]", data.ticketId);
|
||||||
|
//sent notification based on the userSettings
|
||||||
|
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||||
|
NotifType.ANSWER_TICKET,
|
||||||
|
recipientId,
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
|
if (isActive) {
|
||||||
|
await this.smsService.sendAnswerTicketSms(data.userPhone, data.ticketId, data.subject);
|
||||||
|
//send email too
|
||||||
|
}
|
||||||
|
return this.createNotification({ title: NotificationMessage.ANSWER_TICKET, message, recipientId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// //************************ */
|
||||||
|
|
||||||
|
async createTicketNotification(recipientId: string, data: ITicketNotificationData, queryRunner: QueryRunner) {
|
||||||
|
const message = NotificationMessage.CREATE_TICKET_MESSAGE.replace("[ticketId]", data.ticketId);
|
||||||
|
//sent notification based on the userSettings
|
||||||
|
const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(
|
||||||
|
NotifType.CREATE_TICKET,
|
||||||
|
recipientId,
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
|
if (isActive) {
|
||||||
|
await this.smsService.sendCreateTicketSms(data.userPhone, data.ticketId, data.subject, dateFormat(data.date));
|
||||||
|
//send email too
|
||||||
|
}
|
||||||
|
return this.createNotification({ title: NotificationMessage.CREATE_TICKET, message, recipientId });
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
// async createBillInvoiceReminderNotification(recipientId: string, sendNotifData: ISendNotificationData) {
|
||||||
|
// const message = NotificationMessage.BILL_INVOICE_REMINDER_MESSAGE;
|
||||||
|
// //sent notification based on the userSettings
|
||||||
|
// const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BILL_INVOICE_REMINDER, recipientId);
|
||||||
|
// if (isActive) {
|
||||||
|
// await this.smsService.sendBillInvoiceReminderSms(sendNotifData.userPhone, sendNotifData.dueDate, sendNotifData.invoiceId);
|
||||||
|
// //send email too
|
||||||
|
// }
|
||||||
|
// return this.createNotification({ title: NotificationMessage.BILL_INVOICE_REMINDER, message, recipientId });
|
||||||
|
// }
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
// async createBillInvoiceNotification(recipientId: string, sendNotifData: ISendNotificationData) {
|
||||||
|
// const message = NotificationMessage.BILL_INVOICE_MESSAGE;
|
||||||
|
// //sent notification based on the userSettings
|
||||||
|
// const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BILL_INVOICE, recipientId);
|
||||||
|
// if (isActive) {
|
||||||
|
// await this.smsService.sendBillInvoiceSms(sendNotifData.userPhone, sendNotifData.amount, sendNotifData.invoiceId);
|
||||||
|
// //send email too
|
||||||
|
// }
|
||||||
|
// return this.createNotification({ title: NotificationMessage.BILL_INVOICE, message, recipientId });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //************************ */
|
||||||
|
|
||||||
|
// async createServiceNotification(recipientId: string, sendNotifData: ISendNotificationData) {
|
||||||
|
// const message = NotificationMessage.CREATE_SERVICE_MESSAGE.replace("[serviceName]", sendNotifData.serviceName);
|
||||||
|
// //sent notification based on the userSettings
|
||||||
|
// const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.CREATE_SERVICE, recipientId);
|
||||||
|
// if (isActive) {
|
||||||
|
// await this.smsService.sendCreateServiceSms(sendNotifData.userPhone, sendNotifData.serviceName);
|
||||||
|
// //send email too
|
||||||
|
// }
|
||||||
|
// return this.createNotification({ title: NotificationMessage.CREATE_SERVICE, message, recipientId });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //************************ */
|
||||||
|
|
||||||
|
// async createUnblockServiceNotification(recipientId: string, sendNotifData: ISendNotificationData) {
|
||||||
|
// const message = NotificationMessage.UNBLOCK_SERVICE_MESSAGE.replace("[serviceName]", sendNotifData.serviceName);
|
||||||
|
// //sent notification based on the userSettings
|
||||||
|
// const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.UNBLOCK_SERVICE, recipientId);
|
||||||
|
// if (isActive) {
|
||||||
|
// await this.smsService.sendUnblockServiceSms(sendNotifData.userPhone, sendNotifData.serviceName);
|
||||||
|
// //send email too
|
||||||
|
// }
|
||||||
|
// return this.createNotification({ title: NotificationMessage.UNBLOCK_SERVICE, message, recipientId });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //************************ */
|
||||||
|
|
||||||
|
// async createBlockServiceNotification(recipientId: string, sendNotifData: ISendNotificationData) {
|
||||||
|
// const message = NotificationMessage.BLOCK_SERVICE_MESSAGE.replace("[serviceName]", sendNotifData.serviceName);
|
||||||
|
// //sent notification based on the userSettings
|
||||||
|
// const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BLOCK_SERVICE, recipientId);
|
||||||
|
// if (isActive) {
|
||||||
|
// await this.smsService.sendBlockServiceSms(sendNotifData.userPhone, sendNotifData.serviceName);
|
||||||
|
// //send email too
|
||||||
|
// }
|
||||||
|
// return this.createNotification({ title: NotificationMessage.BLOCK_SERVICE, message, recipientId });
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ import { DepositRequest } from "./entities/deposit-request.entity";
|
|||||||
import { PaymentGateway } from "./entities/payment-gateway.entity";
|
import { PaymentGateway } from "./entities/payment-gateway.entity";
|
||||||
import { DepositRequestsRepository } from "./repositories/deposit-requests.repository";
|
import { DepositRequestsRepository } from "./repositories/deposit-requests.repository";
|
||||||
import { PaymentGatewaysRepository } from "./repositories/payment-gateway.repository";
|
import { PaymentGatewaysRepository } from "./repositories/payment-gateway.repository";
|
||||||
|
import { NotificationModule } from "../notifications/notifications.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([Payment, PaymentGateway, BankAccount, DepositRequest]),
|
TypeOrmModule.forFeature([Payment, PaymentGateway, BankAccount, DepositRequest]),
|
||||||
BullModule.registerQueue({ name: PAYMENT.PAYMENT_QUEUE_NAME }),
|
BullModule.registerQueue({ name: PAYMENT.PAYMENT_QUEUE_NAME }),
|
||||||
WalletsModule,
|
WalletsModule,
|
||||||
|
NotificationModule,
|
||||||
],
|
],
|
||||||
controllers: [PaymentsController],
|
controllers: [PaymentsController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { DataSource, Not, QueryRunner } from "typeorm";
|
|||||||
|
|
||||||
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||||
import { CommonMessage, PaymentMessage, UserMessage, WalletMessage } from "../../../common/enums/message.enum";
|
import { CommonMessage, PaymentMessage, UserMessage, WalletMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { NotificationsService } from "../../notifications/providers/notifications.service";
|
||||||
import { User } from "../../users/entities/user.entity";
|
import { User } from "../../users/entities/user.entity";
|
||||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||||
import { WalletsService } from "../../wallets/providers/wallets.service";
|
import { WalletsService } from "../../wallets/providers/wallets.service";
|
||||||
@@ -37,6 +38,7 @@ import { GatewayType } from "../types/gateway.type";
|
|||||||
export class PaymentsService {
|
export class PaymentsService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectQueue(PAYMENT.PAYMENT_QUEUE_NAME) private readonly paymentQueue: Queue,
|
@InjectQueue(PAYMENT.PAYMENT_QUEUE_NAME) private readonly paymentQueue: Queue,
|
||||||
|
private readonly notificationsService: NotificationsService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly gatewayFactory: PaymentGatewayFactory,
|
private readonly gatewayFactory: PaymentGatewayFactory,
|
||||||
private readonly paymentGatewaysRepository: PaymentGatewaysRepository,
|
private readonly paymentGatewaysRepository: PaymentGatewaysRepository,
|
||||||
@@ -159,12 +161,31 @@ export class PaymentsService {
|
|||||||
const verifyData = await paymentGateway.verifyPayment({ reference: queryDto.Authority, amount: payment.amount });
|
const verifyData = await paymentGateway.verifyPayment({ reference: queryDto.Authority, amount: payment.amount });
|
||||||
|
|
||||||
if (verifyData.code === 100) {
|
if (verifyData.code === 100) {
|
||||||
await this.handleSuccessfulPayment(payment.id, payment.user.id, payment.amount, `${verifyData.ref_id}`, queryRunner);
|
const { wallet } = await this.handleSuccessfulPayment(
|
||||||
|
payment.id,
|
||||||
|
payment.user.id,
|
||||||
|
payment.amount,
|
||||||
|
`${verifyData.ref_id}`,
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
frontUrl.searchParams.append("status", PaymentStatus.COMPLETED);
|
frontUrl.searchParams.append("status", PaymentStatus.COMPLETED);
|
||||||
frontUrl.searchParams.append("id", payment.id);
|
frontUrl.searchParams.append("id", payment.id);
|
||||||
frontUrl.searchParams.append("date", payment.createdAt.toISOString());
|
frontUrl.searchParams.append("date", payment.createdAt.toISOString());
|
||||||
frontUrl.searchParams.append("amount", payment.amount.toString());
|
frontUrl.searchParams.append("amount", payment.amount.toString());
|
||||||
|
//
|
||||||
|
await this.notificationsService.createWalletChargeNotification(
|
||||||
|
payment.user.id,
|
||||||
|
{
|
||||||
|
amount: new Decimal(payment.amount).toNumber(),
|
||||||
|
balance: new Decimal(wallet.balance).toNumber(),
|
||||||
|
date: payment.createdAt,
|
||||||
|
reason: WalletMessage.DEPOSIT_WALLET_IPG,
|
||||||
|
userPhone: payment.user.phone,
|
||||||
|
userEmail: payment.user.email,
|
||||||
|
},
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
} else if (verifyData.code === 101) {
|
} else if (verifyData.code === 101) {
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
frontUrl.searchParams.append("status", PaymentStatus.PENDING);
|
frontUrl.searchParams.append("status", PaymentStatus.PENDING);
|
||||||
@@ -229,8 +250,7 @@ export class PaymentsService {
|
|||||||
//*********************************** */
|
//*********************************** */
|
||||||
async handleSuccessfulPayment(paymentId: string, userId: string, amount: Decimal, transactionId: string, queryRunner: QueryRunner) {
|
async handleSuccessfulPayment(paymentId: string, userId: string, amount: Decimal, transactionId: string, queryRunner: QueryRunner) {
|
||||||
await queryRunner.manager.update(Payment, { id: paymentId }, { status: PaymentStatus.COMPLETED, transactionId });
|
await queryRunner.manager.update(Payment, { id: paymentId }, { status: PaymentStatus.COMPLETED, transactionId });
|
||||||
const transaction = await this.walletsService.createPaymentTransaction(userId, amount, queryRunner);
|
return this.walletsService.createPaymentTransaction(userId, amount, queryRunner);
|
||||||
return transaction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//*********************************** */
|
//*********************************** */
|
||||||
|
|||||||
@@ -60,6 +60,23 @@ export class UserSettingsService {
|
|||||||
}
|
}
|
||||||
//*+*******************************************
|
//*+*******************************************
|
||||||
|
|
||||||
|
async getUserNotificationSettingWithType(type: NotifType, userId: string, queryRunner?: QueryRunner) {
|
||||||
|
const repository = queryRunner ? queryRunner.manager.getRepository(UserSetting) : this.userSettingsRepository;
|
||||||
|
|
||||||
|
const setting = await repository.findOne({
|
||||||
|
where: { user: { id: userId }, notificationSetting: { type } },
|
||||||
|
relations: {
|
||||||
|
notificationSetting: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!setting) throw new BadRequestException(SettingMessageEnum.NOT_FOUND);
|
||||||
|
|
||||||
|
return setting;
|
||||||
|
}
|
||||||
|
|
||||||
|
//*+*******************************************
|
||||||
|
|
||||||
async toggleUserNotificationSetting(settingId: string) {
|
async toggleUserNotificationSetting(settingId: string) {
|
||||||
const setting = await this.userSettingsRepository.findOneBy({ id: settingId });
|
const setting = await this.userSettingsRepository.findOneBy({ id: settingId });
|
||||||
|
|
||||||
|
|||||||
@@ -23,4 +23,21 @@ export interface ISmsVerifyBody {
|
|||||||
TemplateId: string;
|
TemplateId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TemplateParams = "VERIFICATIONCODE" | "code" | "invoiceId" | "price" | "items";
|
export type TemplateParams =
|
||||||
|
| "VERIFICATIONCODE"
|
||||||
|
| "code"
|
||||||
|
| "invoiceId"
|
||||||
|
| "price"
|
||||||
|
| "items"
|
||||||
|
| "loginDate"
|
||||||
|
| "invoiceDate"
|
||||||
|
| "title"
|
||||||
|
| "description"
|
||||||
|
| "ticketId"
|
||||||
|
| "serviceName"
|
||||||
|
| "date"
|
||||||
|
| "amount"
|
||||||
|
| "newBalance"
|
||||||
|
| "reason"
|
||||||
|
| "subject"
|
||||||
|
| "message";
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export function numberFormat(number: number, locale: string = "fa-IR") {
|
||||||
|
return Intl.NumberFormat(locale).format(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dateFormat(date: Date, locale: string = "fa-IR") {
|
||||||
|
return new Intl.DateTimeFormat(locale).format(date);
|
||||||
|
}
|
||||||
@@ -84,6 +84,254 @@ export class SmsService {
|
|||||||
throw new InternalServerErrorException("error in sending sms");
|
throw new InternalServerErrorException("error in sending sms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//************************************************* */
|
||||||
|
|
||||||
|
async sendLoginSms(mobile: string, loginDate: string) {
|
||||||
|
const smsData: ISmsVerifyBody = {
|
||||||
|
Parameters: [{ name: "loginDate", value: loginDate }],
|
||||||
|
Mobile: mobile,
|
||||||
|
TemplateId: this.smsConfigs.SMS_PATTERN_LOGIN,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await firstValueFrom(
|
||||||
|
this.httpService
|
||||||
|
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||||
|
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err: AxiosError) => {
|
||||||
|
this.logger.error("error in sending verify sms", err);
|
||||||
|
throw new InternalServerErrorException("error in sending verify sms");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("error in sending sms", error);
|
||||||
|
// throw new InternalServerErrorException("error in sending sms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//************************************************* */
|
||||||
|
|
||||||
|
async sendInvoiceCreationSms(mobile: string, invoiceId: string, price: number, items: string, invoiceDate: string) {
|
||||||
|
const smsData: ISmsVerifyBody = {
|
||||||
|
Parameters: [
|
||||||
|
{ name: "invoiceId", value: invoiceId },
|
||||||
|
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
|
||||||
|
{ name: "items", value: items },
|
||||||
|
{ name: "invoiceDate", value: invoiceDate },
|
||||||
|
],
|
||||||
|
Mobile: mobile,
|
||||||
|
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_CREATED,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await firstValueFrom(
|
||||||
|
this.httpService
|
||||||
|
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||||
|
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err: AxiosError) => {
|
||||||
|
this.logger.error("error in sending invoice created sms", err);
|
||||||
|
throw new InternalServerErrorException("error in sending invoice created sms");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("error in sending sms", error);
|
||||||
|
throw new InternalServerErrorException("error in sending sms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//************************************************* */
|
||||||
|
async sendAnnouncementSms(mobile: string, title: string, description: string, date: string) {
|
||||||
|
const smsData: ISmsVerifyBody = {
|
||||||
|
Parameters: [
|
||||||
|
{ name: "title", value: title },
|
||||||
|
{ name: "description", value: description },
|
||||||
|
{ name: "date", value: date },
|
||||||
|
],
|
||||||
|
Mobile: mobile,
|
||||||
|
TemplateId: this.smsConfigs.SMS_PATTERN_ANNOUNCEMENT,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await firstValueFrom(
|
||||||
|
this.httpService
|
||||||
|
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||||
|
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err: AxiosError) => {
|
||||||
|
this.logger.error("error in sending announcement sms", err);
|
||||||
|
throw new InternalServerErrorException("error in sending announcement sms");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("error in sending sms", error);
|
||||||
|
throw new InternalServerErrorException("error in sending sms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//************************************************* */
|
||||||
|
|
||||||
|
async sendWalletChargeSms(mobile: string, amount: string, newBalance: string, date: string) {
|
||||||
|
const smsData: ISmsVerifyBody = {
|
||||||
|
Parameters: [
|
||||||
|
{ name: "amount", value: amount },
|
||||||
|
{ name: "newBalance", value: newBalance },
|
||||||
|
{ name: "date", value: date },
|
||||||
|
],
|
||||||
|
Mobile: mobile,
|
||||||
|
TemplateId: this.smsConfigs.SMS_PATTERN_WALLET_CHARGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await firstValueFrom(
|
||||||
|
this.httpService
|
||||||
|
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||||
|
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err: AxiosError) => {
|
||||||
|
this.logger.error("error in sending wallet charge sms", err);
|
||||||
|
throw new InternalServerErrorException("error in sending wallet charge sms");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("error in sending sms", error);
|
||||||
|
throw new InternalServerErrorException("error in sending sms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//************************************************* */
|
||||||
|
|
||||||
|
async sendWalletDeductionSms(mobile: string, amount: string, newBalance: string, reason: string, date: string) {
|
||||||
|
const smsData: ISmsVerifyBody = {
|
||||||
|
Parameters: [
|
||||||
|
{ name: "amount", value: amount },
|
||||||
|
{ name: "newBalance", value: newBalance },
|
||||||
|
{ name: "reason", value: reason },
|
||||||
|
{ name: "date", value: date },
|
||||||
|
],
|
||||||
|
Mobile: mobile,
|
||||||
|
TemplateId: this.smsConfigs.SMS_PATTERN_WALLET_DEDUCTION,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await firstValueFrom(
|
||||||
|
this.httpService
|
||||||
|
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||||
|
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err: AxiosError) => {
|
||||||
|
this.logger.error("error in sending wallet deduction sms", err);
|
||||||
|
throw new InternalServerErrorException("error in sending wallet deduction sms");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("error in sending sms", error);
|
||||||
|
throw new InternalServerErrorException("error in sending sms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************* */
|
||||||
|
|
||||||
|
async sendCreateTicketSms(mobile: string, ticketId: string, subject: string, date: string) {
|
||||||
|
const smsData: ISmsVerifyBody = {
|
||||||
|
Parameters: [
|
||||||
|
{ name: "ticketId", value: ticketId },
|
||||||
|
{ name: "subject", value: subject },
|
||||||
|
{ name: "date", value: date },
|
||||||
|
],
|
||||||
|
Mobile: mobile,
|
||||||
|
TemplateId: this.smsConfigs.SMS_PATTERN_TICKET_CREATED,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await firstValueFrom(
|
||||||
|
this.httpService
|
||||||
|
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||||
|
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err: AxiosError) => {
|
||||||
|
this.logger.error("error in sending ticket created sms", err);
|
||||||
|
throw new InternalServerErrorException("error in sending ticket created sms");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("error in sending sms", error);
|
||||||
|
throw new InternalServerErrorException("error in sending sms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//************************************************* */
|
||||||
|
|
||||||
|
async sendAnswerTicketSms(mobile: string, ticketId: string, subject: string) {
|
||||||
|
const smsData: ISmsVerifyBody = {
|
||||||
|
Parameters: [
|
||||||
|
{ name: "ticketId", value: ticketId },
|
||||||
|
{ name: "subject", value: subject },
|
||||||
|
],
|
||||||
|
Mobile: mobile,
|
||||||
|
TemplateId: this.smsConfigs.SMS_PATTERN_TICKET_ANSWERED,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await firstValueFrom(
|
||||||
|
this.httpService
|
||||||
|
.post<ISmsVerifyResponse>(`${this.smsConfigs.API_URL}/send/verify`, smsData, {
|
||||||
|
headers: { "X-API-KEY": this.smsConfigs.API_KEY },
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err: AxiosError) => {
|
||||||
|
this.logger.error("error in sending ticket answered sms", err);
|
||||||
|
throw new InternalServerErrorException("error in sending ticket answered sms");
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("error in sending sms", error);
|
||||||
|
throw new InternalServerErrorException("error in sending sms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************************************* */
|
||||||
|
|
||||||
|
// sendBillInvoiceReminderSms(userPhone: string, dueDate: any, invoiceId: any) {
|
||||||
|
// throw new Error("Method not implemented.");
|
||||||
|
// }
|
||||||
|
// //************************************************* */
|
||||||
|
|
||||||
|
// sendBillInvoiceSms(userPhone: string, amount: any, invoiceId: any) {
|
||||||
|
// throw new Error("Method not implemented.");
|
||||||
|
// }
|
||||||
|
// //************************************************* */
|
||||||
|
|
||||||
|
// sendCreateServiceSms(userPhone: string, serviceName: any) {
|
||||||
|
// throw new Error("Method not implemented.");
|
||||||
|
// }
|
||||||
|
// //************************************************* */
|
||||||
|
|
||||||
|
// sendUnblockServiceSms(userPhone: string, serviceName: any) {
|
||||||
|
// throw new Error("Method not implemented.");
|
||||||
|
// }
|
||||||
|
// //************************************************* */
|
||||||
|
|
||||||
|
// sendBlockServiceSms(userPhone: string, serviceName: any) {
|
||||||
|
// throw new Error("Method not implemented.");
|
||||||
|
// }
|
||||||
|
// //************************************************* */
|
||||||
|
|
||||||
//************************************************* */
|
//************************************************* */
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class WalletsService {
|
|||||||
await queryRunner.manager.save(Wallet, wallet);
|
await queryRunner.manager.save(Wallet, wallet);
|
||||||
await queryRunner.manager.save(WalletTransaction, transaction);
|
await queryRunner.manager.save(WalletTransaction, transaction);
|
||||||
|
|
||||||
return transaction;
|
return { transaction, wallet };
|
||||||
}
|
}
|
||||||
//*********************************** */
|
//*********************************** */
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user