wallet transaction entity added
This commit is contained in:
@@ -169,140 +169,4 @@ export class OrderListeners {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private factory(orderStatus: OrderStatus, event: OrderStatusChangedEvent) {
|
}
|
||||||
// switch (orderStatus) {
|
|
||||||
// case OrderStatus.CONFIRMED:
|
|
||||||
// return {
|
|
||||||
// message: {
|
|
||||||
// title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// content: `Order ${event.orderNumber} confirmed .`,
|
|
||||||
// sms: {
|
|
||||||
// templateId: this.orderCreatedSmsTemplateId,
|
|
||||||
// parameters: {
|
|
||||||
// orderNumber: event.orderNumber,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// pushNotif: {
|
|
||||||
// title: `تایید سفارش`,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} با موفقیت تایید شد`,
|
|
||||||
// icon: `Order ${event.orderNumber} has been confirmed successfully`,
|
|
||||||
// action: {
|
|
||||||
// type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// url: ``,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// case OrderStatus.PREPARING:
|
|
||||||
// return {
|
|
||||||
// message: {
|
|
||||||
// title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} در حال آمادهسازی است`,
|
|
||||||
// sms: {
|
|
||||||
// templateId: this.orderCreatedSmsTemplateId,
|
|
||||||
// parameters: {
|
|
||||||
// orderNumber: event.orderNumber,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// pushNotif: {
|
|
||||||
// title: `آمادهسازی سفارش`,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} در حال آمادهسازی است`,
|
|
||||||
// icon: `Order ${event.orderNumber} is preparing`,
|
|
||||||
// action: {
|
|
||||||
// type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// url: ``,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// case OrderStatus.DELIVERED_TO_RECEPTIONIST:
|
|
||||||
// return {
|
|
||||||
// message: {
|
|
||||||
// title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} آماده است`,
|
|
||||||
// sms: {
|
|
||||||
// templateId: this.orderCreatedSmsTemplateId,
|
|
||||||
// parameters: {
|
|
||||||
// orderNumber: event.orderNumber,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// pushNotif: {
|
|
||||||
// title: `سفارش آماده است`,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} آماده است`,
|
|
||||||
// icon: `Order ${event.orderNumber} is ready`,
|
|
||||||
// action: {
|
|
||||||
// type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// url: ``,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// case OrderStatus.SHIPPED:
|
|
||||||
// return {
|
|
||||||
// message: {
|
|
||||||
// title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} ارسال شد`,
|
|
||||||
// sms: {
|
|
||||||
// templateId: this.orderCreatedSmsTemplateId,
|
|
||||||
// parameters: {
|
|
||||||
// orderNumber: event.orderNumber,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// pushNotif: {
|
|
||||||
// title: `ارسال سفارش`,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} ارسال شد`,
|
|
||||||
// icon: `Order ${event.orderNumber} is shipped`,
|
|
||||||
// action: {
|
|
||||||
// type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// url: ``,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// case OrderStatus.CANCELED:
|
|
||||||
// return {
|
|
||||||
// message: {
|
|
||||||
// title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} لغو شد`,
|
|
||||||
// sms: {
|
|
||||||
// templateId: this.orderCreatedSmsTemplateId,
|
|
||||||
// parameters: {
|
|
||||||
// orderNumber: event.orderNumber,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// pushNotif: {
|
|
||||||
// title: `لغو سفارش`,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} لغو شد`,
|
|
||||||
// icon: `Order ${event.orderNumber} is canceled`,
|
|
||||||
// action: {
|
|
||||||
// type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// url: ``,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// default:
|
|
||||||
// return {
|
|
||||||
// message: {
|
|
||||||
// title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// content: `Order ${event.orderNumber} is ....`,
|
|
||||||
// sms: {
|
|
||||||
// templateId: this.orderCreatedSmsTemplateId,
|
|
||||||
// parameters: {
|
|
||||||
// orderNumber: event.orderNumber,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// pushNotif: {
|
|
||||||
// title: `Order ${event.orderNumber} is ...`,
|
|
||||||
// content: `Order ${event.orderNumber} is ...`,
|
|
||||||
// icon: `Order ${event.orderNumber} is ...`,
|
|
||||||
// action: {
|
|
||||||
// type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// url: ``,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { UpdateUserAddressDto } from '../dto/update-user-address.dto';
|
|||||||
import { UserId } from 'src/common/decorators/user-id.decorator';
|
import { UserId } from 'src/common/decorators/user-id.decorator';
|
||||||
import { RestId } from 'src/common/decorators';
|
import { RestId } from 'src/common/decorators';
|
||||||
import { FindUsersDto } from '../dto/find-user.dto';
|
import { FindUsersDto } from '../dto/find-user.dto';
|
||||||
|
import { FindWalletTransactionsDto } from '../dto/find-wallet-transactions.dto';
|
||||||
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||||
import { API_HEADER_SLUG } from 'src/common/constants/index';
|
import { API_HEADER_SLUG } from 'src/common/constants/index';
|
||||||
import { UserSuccessMessage } from 'src/common/enums/message.enum';
|
import { UserSuccessMessage } from 'src/common/enums/message.enum';
|
||||||
@@ -60,6 +61,21 @@ export class UsersController {
|
|||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Get User Wallet Transactions' })
|
||||||
|
@ApiHeader(API_HEADER_SLUG)
|
||||||
|
@Get('public/user/wallet/transactions')
|
||||||
|
async getUserWalletTransactions(
|
||||||
|
@UserId() userId: string,
|
||||||
|
@RestId() restId: string,
|
||||||
|
@Query(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
query: FindWalletTransactionsDto,
|
||||||
|
) {
|
||||||
|
const transactions = await this.userService.getUserWalletTransactions(userId, restId, query);
|
||||||
|
return transactions;
|
||||||
|
}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiOperation({ summary: 'Create a new address for the authenticated user' })
|
@ApiOperation({ summary: 'Create a new address for the authenticated user' })
|
||||||
@@ -142,7 +158,7 @@ export class UsersController {
|
|||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@Permissions(Permission.MANAGE_USERS)
|
@Permissions(Permission.MANAGE_USERS)
|
||||||
@ApiOperation({ summary: 'Get paginated list of restuarant users with filters' })
|
@ApiOperation({ summary: 'Get paginated list of restuarant users with filters' })
|
||||||
@Get('admin/users')
|
@Get('admin/users')
|
||||||
async findAllRestaurantUsers(
|
async findAllRestaurantUsers(
|
||||||
@Query(new ValidationPipe({ transform: true, whitelist: true }))
|
@Query(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
query: FindUsersDto,
|
query: FindUsersDto,
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { IsNotEmpty, IsNumber, IsIn } from 'class-validator';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { WalletTransactionType, WalletTransactionReason } from '../interface/wallet';
|
||||||
|
|
||||||
|
export class CreateWalletTransactionDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Transaction amount',
|
||||||
|
type: Number,
|
||||||
|
example: 1000,
|
||||||
|
})
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsNumber()
|
||||||
|
@Type(() => Number)
|
||||||
|
amount!: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Transaction type',
|
||||||
|
enum: WalletTransactionType,
|
||||||
|
example: WalletTransactionType.CREDIT,
|
||||||
|
})
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsIn(Object.values(WalletTransactionType))
|
||||||
|
type!: WalletTransactionType;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Transaction reason',
|
||||||
|
enum: WalletTransactionReason,
|
||||||
|
example: WalletTransactionReason.ORDER_PAYMENT,
|
||||||
|
})
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsIn(Object.values(WalletTransactionReason))
|
||||||
|
reason!: WalletTransactionReason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import { IsOptional, IsString, IsNumber, Min, IsIn, IsDateString } from 'class-validator';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { WalletTransactionType, WalletTransactionReason } from '../interface/wallet';
|
||||||
|
|
||||||
|
// Define the valid sort directions
|
||||||
|
const sortOrderOptions = ['asc', 'desc'] as const;
|
||||||
|
type SortOrder = (typeof sortOrderOptions)[number];
|
||||||
|
|
||||||
|
export class FindWalletTransactionsDto {
|
||||||
|
/**
|
||||||
|
* The page number to retrieve.
|
||||||
|
* @default 1
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Page number',
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
minimum: 1,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
@Min(1)
|
||||||
|
@Type(() => Number)
|
||||||
|
page?: number = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of items per page.
|
||||||
|
* @default 10
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Items per page',
|
||||||
|
type: Number,
|
||||||
|
default: 10,
|
||||||
|
minimum: 1,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
@Min(1)
|
||||||
|
@Type(() => Number)
|
||||||
|
limit?: number = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter by transaction type
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Transaction type filter',
|
||||||
|
enum: WalletTransactionType,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsIn(Object.values(WalletTransactionType))
|
||||||
|
type?: WalletTransactionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter by transaction reason
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Transaction reason filter',
|
||||||
|
enum: WalletTransactionReason,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsIn(Object.values(WalletTransactionReason))
|
||||||
|
reason?: WalletTransactionReason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter transactions from this date onwards
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Start date filter (ISO 8601 format)',
|
||||||
|
type: String,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsDateString()
|
||||||
|
startDate?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter transactions up to this date
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'End date filter (ISO 8601 format)',
|
||||||
|
type: String,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsDateString()
|
||||||
|
endDate?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum amount filter
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Minimum amount filter',
|
||||||
|
type: Number,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
@Type(() => Number)
|
||||||
|
minAmount?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum amount filter
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Maximum amount filter',
|
||||||
|
type: Number,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
@Type(() => Number)
|
||||||
|
maxAmount?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The field to sort the results by.
|
||||||
|
* @default "createdAt"
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Sort field',
|
||||||
|
type: String,
|
||||||
|
default: 'createdAt',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
orderBy?: string = 'createdAt';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The direction to sort the results.
|
||||||
|
* @default "desc"
|
||||||
|
*/
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Sort direction',
|
||||||
|
enum: sortOrderOptions,
|
||||||
|
default: 'desc',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsIn(sortOrderOptions)
|
||||||
|
order?: SortOrder = 'desc';
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Entity, Index, Property, OneToMany, Collection, Cascade, ManyToOne, Enum } from '@mikro-orm/core';
|
||||||
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet';
|
||||||
|
import { UserWallet } from './user-wallet.entity';
|
||||||
|
|
||||||
|
@Entity({ tableName: 'wallet_transactions' })
|
||||||
|
export class WalletTransaction extends BaseEntity {
|
||||||
|
@ManyToOne(() => UserWallet)
|
||||||
|
userWallet!: UserWallet;
|
||||||
|
|
||||||
|
@Property({ type: 'decimal', precision: 10, scale: 0 })
|
||||||
|
amount!: number;
|
||||||
|
|
||||||
|
@Property({ type: 'decimal', precision: 10, scale: 0 })
|
||||||
|
balance!: number;
|
||||||
|
|
||||||
|
@Enum(() => WalletTransactionType)
|
||||||
|
type!: WalletTransactionType;
|
||||||
|
|
||||||
|
@Enum(() => WalletTransactionReason)
|
||||||
|
reason!: WalletTransactionReason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export enum WalletTransactionType {
|
||||||
|
CREDIT = 'credit',
|
||||||
|
DEBIT = 'debit',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export enum WalletTransactionReason {
|
||||||
|
ORDER_PAYMENT = 'order_payment',
|
||||||
|
ORDER_REFUND = 'order_refund',
|
||||||
|
CONVERT_SCORE_TO_WALLET = 'convert_score_to_wallet',
|
||||||
|
WITHDRAW = 'withdraw',
|
||||||
|
DEPOSIT = 'deposit',
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import { UserAddress } from '../entities/user-address.entity';
|
|||||||
import { EntityManager } from '@mikro-orm/postgresql';
|
import { EntityManager } from '@mikro-orm/postgresql';
|
||||||
import { UpdateUserDto } from '../dto/update-user.dto';
|
import { UpdateUserDto } from '../dto/update-user.dto';
|
||||||
import { FindUsersDto } from '../dto/find-user.dto';
|
import { FindUsersDto } from '../dto/find-user.dto';
|
||||||
|
import { FindWalletTransactionsDto } from '../dto/find-wallet-transactions.dto';
|
||||||
|
import { CreateWalletTransactionDto } from '../dto/create-wallet-transaction.dto';
|
||||||
import { CreateUserAddressDto } from '../dto/create-user-address.dto';
|
import { CreateUserAddressDto } from '../dto/create-user-address.dto';
|
||||||
import { UpdateUserAddressDto } from '../dto/update-user-address.dto';
|
import { UpdateUserAddressDto } from '../dto/update-user-address.dto';
|
||||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||||
@@ -13,6 +15,7 @@ import { normalizePhone } from '../../utils/phone.util';
|
|||||||
import { RestRepository } from 'src/modules/restaurants/repositories/rest.repository';
|
import { RestRepository } from 'src/modules/restaurants/repositories/rest.repository';
|
||||||
import { UserWallet } from '../entities/user-wallet.entity';
|
import { UserWallet } from '../entities/user-wallet.entity';
|
||||||
import { UserWalletRepository } from '../repositories/user-wallet.repository';
|
import { UserWalletRepository } from '../repositories/user-wallet.repository';
|
||||||
|
import { WalletService } from './wallet.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
@@ -20,8 +23,9 @@ export class UserService {
|
|||||||
private readonly userRepository: UserRepository,
|
private readonly userRepository: UserRepository,
|
||||||
private readonly restaurantRepository: RestRepository,
|
private readonly restaurantRepository: RestRepository,
|
||||||
private readonly userWalletRepository: UserWalletRepository,
|
private readonly userWalletRepository: UserWalletRepository,
|
||||||
|
private readonly walletService: WalletService,
|
||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
async findOrCreateByPhone(phone: string): Promise<User> {
|
async findOrCreateByPhone(phone: string): Promise<User> {
|
||||||
const normalizedPhone = normalizePhone(phone);
|
const normalizedPhone = normalizePhone(phone);
|
||||||
@@ -343,6 +347,15 @@ export class UserService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
getUserWallet(userId: string, restId: string): Promise<UserWallet | null> {
|
getUserWallet(userId: string, restId: string): Promise<UserWallet | null> {
|
||||||
return this.userWalletRepository.findOne({ user: { id: userId }, restaurant: { id: restId } });
|
return this.walletService.getUserWallet(userId, restId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUserWalletTransactions(userId: string, restId: string, dto: FindWalletTransactionsDto) {
|
||||||
|
return this.walletService.getUserWalletTransactions(userId, restId, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
createWalletTransaction(userId: string, restId: string, dto: CreateWalletTransactionDto) {
|
||||||
|
return this.walletService.createTransaction(userId, restId, dto);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||||
|
import { FilterQuery } from '@mikro-orm/core';
|
||||||
|
import { EntityManager } from '@mikro-orm/postgresql';
|
||||||
|
import { WalletTransaction } from '../entities/wallet-transaction.entity';
|
||||||
|
import { UserWallet } from '../entities/user-wallet.entity';
|
||||||
|
import { WalletTransactionRepository } from '../repositories/wallet-transaction.repository';
|
||||||
|
import { UserWalletRepository } from '../repositories/user-wallet.repository';
|
||||||
|
import { FindWalletTransactionsDto } from '../dto/find-wallet-transactions.dto';
|
||||||
|
import { CreateWalletTransactionDto } from '../dto/create-wallet-transaction.dto';
|
||||||
|
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||||
|
import { WalletTransactionType } from '../interface/wallet';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WalletService {
|
||||||
|
constructor(
|
||||||
|
private readonly walletTransactionRepository: WalletTransactionRepository,
|
||||||
|
private readonly userWalletRepository: UserWalletRepository,
|
||||||
|
private readonly em: EntityManager,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async getUserWalletTransactions(
|
||||||
|
userId: string,
|
||||||
|
restId: string,
|
||||||
|
dto: FindWalletTransactionsDto,
|
||||||
|
): Promise<PaginatedResult<WalletTransaction>> {
|
||||||
|
const {
|
||||||
|
page = 1,
|
||||||
|
limit = 10,
|
||||||
|
type,
|
||||||
|
reason,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
minAmount,
|
||||||
|
maxAmount,
|
||||||
|
orderBy = 'createdAt',
|
||||||
|
order = 'desc',
|
||||||
|
} = dto;
|
||||||
|
|
||||||
|
// Calculate pagination
|
||||||
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
|
// Find the user's wallet for this restaurant
|
||||||
|
const userWallet = await this.userWalletRepository.findOne({
|
||||||
|
user: { id: userId },
|
||||||
|
restaurant: { id: restId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userWallet) {
|
||||||
|
throw new NotFoundException(`User wallet not found for user ${userId} and restaurant ${restId}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the 'where' filter query
|
||||||
|
const where: FilterQuery<WalletTransaction> = {
|
||||||
|
userWallet: { id: userWallet.id },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add type filter
|
||||||
|
if (type) {
|
||||||
|
where.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add reason filter
|
||||||
|
if (reason) {
|
||||||
|
where.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add date range filters
|
||||||
|
if (startDate || endDate) {
|
||||||
|
where.createdAt = {};
|
||||||
|
if (startDate) {
|
||||||
|
where.createdAt.$gte = new Date(startDate);
|
||||||
|
}
|
||||||
|
if (endDate) {
|
||||||
|
where.createdAt.$lte = new Date(endDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add amount range filters
|
||||||
|
if (minAmount !== undefined || maxAmount !== undefined) {
|
||||||
|
where.amount = {};
|
||||||
|
if (minAmount !== undefined) {
|
||||||
|
where.amount.$gte = minAmount;
|
||||||
|
}
|
||||||
|
if (maxAmount !== undefined) {
|
||||||
|
where.amount.$lte = maxAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the query using findAndCount
|
||||||
|
const [transactions, total] = await this.walletTransactionRepository.findAndCount(where, {
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate total pages
|
||||||
|
const totalPages = Math.ceil(total / limit);
|
||||||
|
|
||||||
|
// Return the paginated result
|
||||||
|
return {
|
||||||
|
data: transactions,
|
||||||
|
meta: {
|
||||||
|
total,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
totalPages,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserWallet(userId: string, restId: string): Promise<UserWallet | null> {
|
||||||
|
return this.userWalletRepository.findOne({ user: { id: userId }, restaurant: { id: restId } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async createTransaction(
|
||||||
|
userId: string,
|
||||||
|
restId: string,
|
||||||
|
dto: CreateWalletTransactionDto,
|
||||||
|
): Promise<WalletTransaction> {
|
||||||
|
const { amount, type, reason } = dto;
|
||||||
|
|
||||||
|
// Validate amount
|
||||||
|
if (amount <= 0) {
|
||||||
|
throw new BadRequestException('Transaction amount must be greater than 0.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the user's wallet for this restaurant
|
||||||
|
const userWallet = await this.userWalletRepository.findOne({
|
||||||
|
user: { id: userId },
|
||||||
|
restaurant: { id: restId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userWallet) {
|
||||||
|
throw new NotFoundException(`User wallet not found for user ${userId} and restaurant ${restId}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new balance
|
||||||
|
const currentBalance = userWallet.wallet || 0;
|
||||||
|
let newBalance: number;
|
||||||
|
|
||||||
|
if (type === WalletTransactionType.CREDIT) {
|
||||||
|
newBalance = currentBalance + amount;
|
||||||
|
} else if (type === WalletTransactionType.DEBIT) {
|
||||||
|
if (currentBalance < amount) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Insufficient wallet balance. Current: ${currentBalance}, Required: ${amount}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
newBalance = currentBalance - amount;
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException(`Invalid transaction type: ${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use transaction to ensure consistency
|
||||||
|
return await this.em.transactional(async (em) => {
|
||||||
|
// Update wallet balance
|
||||||
|
userWallet.wallet = newBalance;
|
||||||
|
await em.persistAndFlush(userWallet);
|
||||||
|
|
||||||
|
// Create transaction record
|
||||||
|
const transaction = em.create(WalletTransaction, {
|
||||||
|
userWallet,
|
||||||
|
amount,
|
||||||
|
balance: newBalance,
|
||||||
|
type,
|
||||||
|
reason,
|
||||||
|
});
|
||||||
|
|
||||||
|
await em.persistAndFlush(transaction);
|
||||||
|
|
||||||
|
return transaction;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
||||||
|
import { WalletTransaction } from '../entities/wallet-transaction.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WalletTransactionRepository extends EntityRepository<WalletTransaction> {
|
||||||
|
constructor(readonly em: EntityManager) {
|
||||||
|
super(em, WalletTransaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,12 +8,16 @@ import { JwtModule } from '@nestjs/jwt';
|
|||||||
import { UserRepository } from './repositories/user.repository';
|
import { UserRepository } from './repositories/user.repository';
|
||||||
import { RestaurantsModule } from '../restaurants/restaurants.module';
|
import { RestaurantsModule } from '../restaurants/restaurants.module';
|
||||||
import { UserWalletRepository } from './repositories/user-wallet.repository';
|
import { UserWalletRepository } from './repositories/user-wallet.repository';
|
||||||
|
import { WalletTransactionRepository } from './repositories/wallet-transaction.repository';
|
||||||
|
import { WalletService } from './providers/wallet.service';
|
||||||
import { UserWallet } from './entities/user-wallet.entity';
|
import { UserWallet } from './entities/user-wallet.entity';
|
||||||
|
import { WalletTransaction } from './entities/wallet-transaction.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [UserService, UserRepository, UserWalletRepository],
|
providers: [UserService, WalletService, UserRepository, UserWalletRepository, WalletTransactionRepository],
|
||||||
controllers: [UsersController],
|
controllers: [UsersController],
|
||||||
imports: [MikroOrmModule.forFeature([User, UserAddress, UserWallet]), JwtModule, RestaurantsModule],
|
imports: [MikroOrmModule.forFeature([User, UserAddress, UserWallet,
|
||||||
exports: [UserService, UserRepository, UserWalletRepository],
|
WalletTransaction]), JwtModule, RestaurantsModule],
|
||||||
|
exports: [UserService, WalletService, UserRepository, UserWalletRepository, WalletTransactionRepository],
|
||||||
})
|
})
|
||||||
export class UserModule {}
|
export class UserModule { }
|
||||||
|
|||||||
Reference in New Issue
Block a user