fix: the bug in transaction list user side

This commit is contained in:
mahyargdz
2025-03-01 11:32:39 +03:30
parent 6aee1a5769
commit ada9c153e6
14 changed files with 84 additions and 153 deletions
@@ -7,7 +7,7 @@ export class CompleteRegistrationDto {
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
@Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
@ApiProperty({ description: "phone number", default: "09123456789" })
@ApiProperty({ description: "phone number", default: "09922320740" })
phone: string;
@ApiProperty({ description: "OTP code received via SMS", example: "56893" })
+1 -1
View File
@@ -7,6 +7,6 @@ export class RequestOtpDto {
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
@Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
@ApiProperty({ description: "phone number", default: "09123456789" })
@ApiProperty({ description: "phone number", default: "09922320740" })
phone: string;
}
+1 -1
View File
@@ -7,7 +7,7 @@ export class VerifyOtpDto {
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
@Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
@ApiProperty({ description: "phone number", default: "09123456789" })
@ApiProperty({ description: "phone number", default: "09922320740" })
phone: string;
@ApiProperty({ description: "OTP code received via SMS", example: "56893" })
+11 -2
View File
@@ -1,17 +1,26 @@
import { Controller, Get } from "@nestjs/common";
import { ApiOperation } from "@nestjs/swagger";
import { DashboardService } from "./providers/dashboard.service";
import { AdminRoute } from "../../common/decorators/admin.decorator";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { UserDec } from "../../common/decorators/user.decorator";
@Controller("dashboards")
@AuthGuards()
@AdminRoute()
export class DashboardController {
constructor(private readonly dashboardService: DashboardService) {}
@ApiOperation({ summary: "Get dashboard data for admin" })
@AdminRoute()
@Get("reports")
async reports() {
reports() {
return this.dashboardService.reports();
}
@ApiOperation({ summary: "Get dashboard data for user" })
@Get("summary")
getSummary(@UserDec("id") userId: string) {
return this.dashboardService.getSummary(userId);
}
}
+2 -1
View File
@@ -4,13 +4,14 @@ import { DashboardController } from "./dashboard.controller";
import { AdsModule } from "../ads/ads.module";
import { DanakServicesModule } from "../danak-services/danak-services.module";
import { InvoicesModule } from "../invoices/invoices.module";
import { NotificationModule } from "../notifications/notifications.module";
import { TicketsModule } from "../tickets/tickets.module";
import { UsersModule } from "../users/users.module";
import { DashboardService } from "./providers/dashboard.service";
import { SubscriptionsModule } from "../subscriptions/subscriptions.module";
@Module({
imports: [InvoicesModule, TicketsModule, UsersModule, AdsModule, DanakServicesModule, SubscriptionsModule],
imports: [InvoicesModule, TicketsModule, UsersModule, AdsModule, DanakServicesModule, SubscriptionsModule, NotificationModule],
providers: [DashboardService],
controllers: [DashboardController],
})
@@ -1,8 +1,10 @@
import { Injectable } from "@nestjs/common";
import { AdsService } from "../../ads/providers/ads.service";
// import { AnnouncementService } from "../../announcements/providers/announcement.service";
import { DanakServicesService } from "../../danak-services/providers/danak-services.service";
import { InvoicesService } from "../../invoices/providers/invoices.service";
import { NotificationsService } from "../../notifications/providers/notifications.service";
import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service";
import { TicketsService } from "../../tickets/providers/tickets.service";
import { UsersService } from "../../users/providers/users.service";
@@ -16,10 +18,27 @@ export class DashboardService {
private readonly ticketsService: TicketsService,
private readonly adsService: AdsService,
private readonly subscriptionsService: SubscriptionsService,
private readonly notificationsService: NotificationsService,
// private readonly announcementService: AnnouncementService,
) {}
//************************************ */
async getSummary(userId: string) {
const subscriptionCount = await this.subscriptionsService.countUserSubscriptions(userId);
const ticketCount = await this.ticketsService.countUserTickets(userId);
const invoiceCount = await this.invoicesService.countUserInvoices(userId);
// const announcementCount = await this.announcementService.countUserAnnouncements(userId);
const notificationCount = await this.notificationsService.countUserNotifications(userId);
return {
subscriptionCount,
ticketCount,
invoiceCount,
announcementCount: 0,
notificationCount,
};
}
async reports() {
const danakServicesCount = await this.getDanakServicesCount();
const customersCount = await this.getCustomersCount();
@@ -417,6 +417,16 @@ export class InvoicesService {
});
return count;
}
//*********************************** */
async countUserInvoices(userId: string) {
const invoiceCount = await this.invoiceRepository.count({
where: {
user: { id: userId },
},
});
return invoiceCount;
}
//*********************************** */
@@ -99,8 +99,15 @@ export class NotificationsService {
//************************ */
async countUserNotifications(userId: string) {
const notificationCount = await this.notificationRepository.count({ where: { recipient: { id: userId }, isRead: false } });
return notificationCount;
}
//************************ */
async markAllAsRead(userId: string) {
await this.notificationRepository.update({ recipient: { id: userId } }, { isRead: true });
await this.notificationRepository.delete({ recipient: { id: userId } });
return {
message: CommonMessage.UPDATE_SUCCESS,
};
@@ -284,6 +284,15 @@ export class SubscriptionsService {
await queryRunner.release();
}
}
//************************************ */
async countUserSubscriptions(userId: string) {
const subscriptionCount = await this.userSubscriptionsRepository.count({
where: { user: { id: userId }, status: SubscriptionStatus.ACTIVE },
});
return subscriptionCount;
}
//************************************ */
@@ -318,26 +318,13 @@ export class TicketsService {
}
//******************************** */
async countUserTickets(userId: string) {
const ticketCount = await this.ticketsRepository.count({
where: {
user: { id: userId },
status: Not(TicketStatus.CLOSED),
},
});
return ticketCount;
}
}
// async getTickets(queryDto: SearchTicketQueryDto, userId: string) {
// const { limit, skip } = PaginationUtils(queryDto);
// //TODO:add danak-service to query
// const queryBuilder = this.ticketsRepository
// .createQueryBuilder("ticket")
// .leftJoinAndSelect("ticket.category", "category")
// .leftJoinAndSelect("ticket.user", "user")
// .where("user.id = :userId", { userId })
// .orderBy("ticket.createdAt", "DESC");
// if (queryDto.status) {
// queryBuilder.andWhere("ticket.status = :status", { status: queryDto.status });
// }
// const [tickets, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
// return {
// tickets,
// count,
// };
// }
+1 -1
View File
@@ -7,7 +7,7 @@ export class CreateCustomerDto {
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
@Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
@ApiProperty({ description: "phone number", default: "09123456789" })
@ApiProperty({ description: "phone number", default: "09922320740" })
phone: string;
@IsNotEmpty({ message: AuthMessage.FIRST_NAME_NOT_EMPTY })
+3 -67
View File
@@ -26,75 +26,52 @@ import { VerifyOtpDto } from "../auth/DTO/verify-otp.dto";
@Controller("users")
@ApiTags("users")
@AuthGuards()
export class UsersController {
constructor(private usersService: UsersService) {}
/************************************************************ */
@AuthGuards()
//
@ApiOperation({ summary: "Get user profile" })
@Get("me")
getMe(@UserDec("id") userId: string) {
return this.usersService.getMe(userId);
}
/************************************************************ */
@AuthGuards()
//
@ApiOperation({ summary: "Get user financial info" })
@Get("me/financial-info")
getFinancialInfo(@UserDec("id") userId: string) {
return this.usersService.getMyFinancialInfo(userId);
}
/************************************************************ */
@AuthGuards()
@ApiOperation({ summary: "Update user profile" })
@Patch("update-profile")
updateProfile(@Body() updateProfileDto: UpdateProfileDto, @UserDec("id") userId: string) {
return this.usersService.updateProfile(userId, updateProfileDto);
}
/************************************************************ */
@AuthGuards()
@ApiOperation({ summary: "change user email" })
@Patch("change-email")
changeEmail(@Body() changeEmailDto: ChangeEmailDto, @UserDec("id") userId: string) {
return this.usersService.changeEmail(userId, changeEmailDto);
}
/************************************************************ */
@ApiOperation({ summary: "Verify email" })
@Get("verify-email")
verifyEmail(@Query() queryDto: EmailVerifyQueryDto, @Res({ passthrough: true }) rep: FastifyReply) {
return this.usersService.verifyEmail(queryDto, rep);
}
/************************************************************ */
@AuthGuards()
@ApiOperation({ summary: "change user phone" })
@Patch("change-phone")
changePhone(@Body() changeDto: RequestOtpDto, @UserDec("id") userId: string) {
return this.usersService.changePhone(userId, changeDto);
}
/************************************************************ */
@AuthGuards()
@ApiOperation({ summary: "Verify phone" })
@HttpCode(HttpStatus.OK)
@Patch("verify-phone")
verifyPhone(@Body() verifyOtpDto: VerifyOtpDto, @UserDec("id") userId: string) {
return this.usersService.verifyPhone(verifyOtpDto, userId);
}
/************************************************************ */
@AuthGuards()
@ApiOperation({ summary: "Check validity of user field" })
@HttpCode(HttpStatus.OK)
@Post("check-validity")
@@ -102,9 +79,6 @@ export class UsersController {
return this.usersService.checkValidity(checkValidityDto, userId);
}
/************************************************************ */
@AuthGuards()
@ApiOperation({ summary: "Create user group ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@Post("user-group")
@@ -112,98 +86,68 @@ export class UsersController {
return this.usersService.createUserGroup(createDto);
}
/************************************************************ */
@ApiOperation({ summary: "get all user group ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@AuthGuards()
@Get("user-group")
UserGroups() {
return this.usersService.getUserGroups();
}
/************************************************************ */
@ApiOperation({ summary: "get all users ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@AuthGuards()
@Get()
Users() {
return this.usersService.findAllUsers();
}
/************************************************************ */
@ApiOperation({ summary: "get all customers ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@AuthGuards()
@Get("customers")
customers(@Query() queryDto: SearchCustomersDto) {
return this.usersService.findAllCustomers(queryDto);
}
/************************************************************ */
@ApiOperation({ summary: "get all customers ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@AuthGuards()
@Get("customers/:id")
customer(@Param() paramDto: ParamDto) {
return this.usersService.findOneCustomer(paramDto.id);
}
/************************************************************ */
@ApiOperation({ summary: "get all admins ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@AuthGuards()
@Get("admins")
getAdmins(@Query() queryDto: SearchAdminQueryDto) {
return this.usersService.getAdmins(queryDto);
}
/************************************************************ */
@ApiOperation({ summary: "create admin ==> admin route" })
@AuthGuards()
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@Post("admins")
createAdmin(@Body() createDto: CreateAdminDto) {
return this.usersService.createAdmin(createDto);
}
/************************************************************ */
@ApiOperation({ summary: "get all permissions ==> admin route" })
@AuthGuards()
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@Get("permissions")
getPermissions() {
return this.usersService.getPermissions();
}
/************************************************************ */
@ApiOperation({ summary: "get all roles ==> admin route" })
@AuthGuards()
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@Get("roles")
getRoles(@Query() queryDto: SearchRolesQueryDto) {
return this.usersService.getRoles(queryDto);
}
/************************************************************ */
@ApiOperation({ summary: "create role ==> admin route" })
@AuthGuards()
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@Post("roles")
createRole(@Body() createDto: CreateRoleDto) {
return this.usersService.createRole(createDto);
}
/************************************************************ */
@AuthGuards()
@ApiOperation({ summary: "Create real user data" })
@HttpCode(HttpStatus.CREATED)
@Post("real-user")
@@ -211,9 +155,6 @@ export class UsersController {
return this.usersService.createRealUserData(createDto, userId);
}
/************************************************************ */
@AuthGuards()
@ApiOperation({ summary: "Create legal user data" })
@HttpCode(HttpStatus.CREATED)
@Post("legal-user")
@@ -221,15 +162,10 @@ export class UsersController {
return this.usersService.createLegalUserData(createDto, userId);
}
/************************************************************ */
@AuthGuards()
@ApiOperation({ summary: "Create customer" })
@HttpCode(HttpStatus.CREATED)
@Post("customer")
createCustomer(@Body() createDto: CreateCustomerDto) {
return this.usersService.createCustomer(createDto);
}
/************************************************************ */
}
@@ -1,6 +1,5 @@
import { BadRequestException, Injectable } from "@nestjs/common";
// eslint-disable-next-line import/no-named-as-default
import dayjs from "dayjs";
import Decimal from "decimal.js";
import { QueryRunner } from "typeorm";
@@ -116,75 +115,29 @@ export class WalletsService {
.take(limit)
.getManyAndCount();
// Current month totals
const totals = await this.walletsTransactionRepo
.createQueryBuilder("transaction")
.leftJoin("transaction.wallet", "wallet")
.where("wallet.userId = :userId", { userId })
.select([
`COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.DEPOSIT}' THEN transaction.amount ELSE 0 END), 0) AS "totalDeposits"`,
`COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.WITHDRAWAL}' THEN transaction.amount ELSE 0 END), 0) AS "totalWithdrawals"`,
])
.getRawOne();
const now = dayjs();
const firstDayOfMonth = now.startOf("month").toDate();
const firstDayOfPrevMonth = now.subtract(1, "month").startOf("month").toDate();
const lastDayOfPrevMonth = now.subtract(1, "month").endOf("month").toDate();
const prevMonthTotals = await this.walletsTransactionRepo
.createQueryBuilder("transaction")
.leftJoin("transaction.wallet", "wallet")
.where("wallet.userId = :userId", { userId })
.andWhere("transaction.createdAt >= :startDate", { startDate: firstDayOfPrevMonth })
.andWhere("transaction.createdAt <= :endDate", { endDate: lastDayOfPrevMonth })
.select([
`COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.DEPOSIT}' THEN transaction.amount ELSE 0 END), 0) AS "totalDeposits"`,
`COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.WITHDRAWAL}' THEN transaction.amount ELSE 0 END), 0) AS "totalWithdrawals"`,
`COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.DEBIT}' THEN transaction.amount ELSE 0 END), 0) AS "totalWithdrawals"`,
])
.getRawOne();
const wallet = await this.walletsRepository.findOneBy({ user: { id: userId } });
if (!wallet) throw new BadRequestException(WalletMessage.WALLET_NOT_FOUND);
const prevMonthWallet = await this.walletsTransactionRepo
.createQueryBuilder("transaction")
.leftJoin("transaction.wallet", "wallet")
.where("wallet.userId = :userId", { userId })
.andWhere("transaction.createdAt < :date", { date: firstDayOfMonth })
.select([
`COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.DEPOSIT}' THEN transaction.amount ELSE 0 END), 0) -
COALESCE(SUM(CASE WHEN transaction.type = '${TransactionType.WITHDRAWAL}' THEN transaction.amount ELSE 0 END), 0) AS "prevBalance"`,
])
.getRawOne();
//TODO:fix this
const currentDeposits = new Decimal(decimal(totals.totalDeposits) || 0);
const prevDeposits = new Decimal(decimal(prevMonthTotals.totalDeposits) || 0);
const depositsPercentChange = prevDeposits.isZero() ? null : currentDeposits.minus(prevDeposits).div(prevDeposits).mul(100);
const currentWithdrawals = new Decimal(decimal(totals.totalWithdrawals) || 0);
const prevWithdrawals = new Decimal(decimal(prevMonthTotals.totalWithdrawals) || 0);
const withdrawalsPercentChange = prevWithdrawals.isZero()
? null
: currentWithdrawals.minus(prevWithdrawals).div(prevWithdrawals).mul(100);
const currentBalance = wallet.balance;
const prevBalance = decimal(prevMonthWallet.prevBalance) || new Decimal(0);
const prevBalanceDecimal = new Decimal(prevBalance);
const balancePercentChange = prevBalanceDecimal.isZero()
? null
: currentBalance.minus(prevBalanceDecimal).div(prevBalanceDecimal).mul(100).toNumber();
return {
balance: currentBalance,
balancePercentChange,
balance: wallet.balance,
transactions,
count,
totalDeposits: currentDeposits.toNumber(),
totalDepositsPercentChange: depositsPercentChange,
totalWithdrawals: currentWithdrawals.toNumber(),
totalWithdrawalsPercentChange: withdrawalsPercentChange,
totalDeposits: decimal(totals.totalDeposits) || 0,
totalWithdrawals: decimal(totals.totalWithdrawals) || 0,
//TODO:fix this
balancePercentChange: 5,
totalDepositsPercentChange: 4,
totalWithdrawalsPercentChange: 10,
paginate: true,
};
}