rerfactor user wallet ans user points

This commit is contained in:
2025-12-29 21:31:01 +03:30
parent cfc0366eca
commit 0f1f472adb
17 changed files with 193 additions and 116 deletions
+5 -3
View File
@@ -7,7 +7,6 @@ import { Restaurant } from '../restaurants/entities/restaurant.entity';
import { PaymentMethod } from '../payments/entities/payment-method.entity'; import { PaymentMethod } from '../payments/entities/payment-method.entity';
import { UserAddress } from '../users/entities/user-address.entity'; import { UserAddress } from '../users/entities/user-address.entity';
import { User } from '../users/entities/user.entity'; import { User } from '../users/entities/user.entity';
import { UserWallet } from '../users/entities/user-wallet.entity';
import { Delivery } from '../delivery/entities/delivery.entity'; import { Delivery } from '../delivery/entities/delivery.entity';
import { Order } from '../orders/entities/order.entity'; import { Order } from '../orders/entities/order.entity';
import { AuthModule } from '../auth/auth.module'; import { AuthModule } from '../auth/auth.module';
@@ -18,6 +17,8 @@ import { CartRepository } from './repositories/cart.repository';
import { CartValidationService } from './providers/cart-validation.service'; import { CartValidationService } from './providers/cart-validation.service';
import { CartCalculationService } from './providers/cart-calculation.service'; import { CartCalculationService } from './providers/cart-calculation.service';
import { CartItemService } from './providers/cart-item.service'; import { CartItemService } from './providers/cart-item.service';
import { PointTransaction } from '../users/entities/point-transaction.entity';
import { WalletTransaction } from '../users/entities/wallet-transaction.entity';
@Module({ @Module({
imports: [ imports: [
@@ -27,7 +28,8 @@ import { CartItemService } from './providers/cart-item.service';
PaymentMethod, PaymentMethod,
UserAddress, UserAddress,
User, User,
UserWallet, PointTransaction,
WalletTransaction,
Delivery, Delivery,
Order, Order,
]), ]),
@@ -46,4 +48,4 @@ import { CartItemService } from './providers/cart-item.service';
], ],
exports: [CartService], exports: [CartService],
}) })
export class CartModule {} export class CartModule { }
@@ -7,17 +7,21 @@ import { User } from '../../users/entities/user.entity';
import { PaymentMethod } from '../../payments/entities/payment-method.entity'; import { PaymentMethod } from '../../payments/entities/payment-method.entity';
import { Delivery } from '../../delivery/entities/delivery.entity'; import { Delivery } from '../../delivery/entities/delivery.entity';
import { DeliveryMethodEnum } from '../../delivery/interface/delivery'; import { DeliveryMethodEnum } from '../../delivery/interface/delivery';
import { UserWallet } from '../../users/entities/user-wallet.entity'; import { PointTransaction } from '../../users/entities/point-transaction.entity';
import { Order } from '../../orders/entities/order.entity'; import { Order } from '../../orders/entities/order.entity';
import { OrderStatus } from '../../orders/interface/order.interface'; import { OrderStatus } from '../../orders/interface/order.interface';
import { Cart } from '../interfaces/cart.interface'; import { Cart } from '../interfaces/cart.interface';
import { GeographicUtils } from '../utils/geographic.utils'; import { GeographicUtils } from '../utils/geographic.utils';
import { MealType } from '../../foods/interface/food.interface'; import { MealType } from '../../foods/interface/food.interface';
import { CartMessage } from 'src/common/enums/message.enum'; import { CartMessage } from 'src/common/enums/message.enum';
import { WalletTransactionRepository } from 'src/modules/users/repositories/wallet-transaction.repository';
@Injectable() @Injectable()
export class CartValidationService { export class CartValidationService {
constructor(private readonly em: EntityManager) { } constructor(
private readonly em: EntityManager,
private readonly walletTransactionRepository: WalletTransactionRepository,
) { }
/** /**
* Validate food exists, belongs to restaurant, has inventory, and check stock * Validate food exists, belongs to restaurant, has inventory, and check stock
@@ -216,16 +220,9 @@ export class CartValidationService {
* Assert wallet has enough balance * Assert wallet has enough balance
*/ */
async assertWalletHasEnoughBalance(userId: string, restaurantId: string, amount: number): Promise<void> { async assertWalletHasEnoughBalance(userId: string, restaurantId: string, amount: number): Promise<void> {
const wallet = await this.em.findOne(UserWallet, { const balance = await this.walletTransactionRepository.getCurrentWalletBalance(userId, restaurantId);
user: { id: userId },
restaurant: { id: restaurantId },
});
if (!wallet) { if (balance < amount) {
throw new BadRequestException(CartMessage.WALLET_NOT_FOUND);
}
if (wallet.wallet < amount) {
throw new BadRequestException(CartMessage.WALLET_INSUFFICIENT); throw new BadRequestException(CartMessage.WALLET_INSUFFICIENT);
} }
} }
+29 -12
View File
@@ -117,18 +117,35 @@ export class OrderListeners {
`User not found for order: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, `User not found for order: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`,
); );
} }
const order = await this.OrderRepository.findOne(event.orderId);
if (!order) { // const restaurant = await this.RestaurantRepository.findOne(event.restaurantId);
this.logger.log( // if (!restaurant) {
`Order not found for order: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, // this.logger.log(
); // `Restaurant not found for order: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`,
return; // );
} // return;
this.userService.createWalletTransaction(event.userId, event.restaurantId, { // }
amount: order.subTotal, // const score = restaurant.score;
type: WalletTransactionType.CREDIT, // if (!score) {
reason: WalletTransactionReason.ORDER_COMPLETED_DEPOSIT, // this.logger.log(
}); // `Score not found for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`,
// );
// return;
// }
// increase score for user
// const order = await this.OrderRepository.findOne(event.orderId);
// if (!order) {
// this.logger.log(
// `Order not found for order: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`,
// );
// return;
// }
// this.userService.createWalletTransaction(event.userId, event.restaurantId, {
// amount: order.subTotal,
// type: WalletTransactionType.CREDIT,
// reason: WalletTransactionReason.ORDER_COMPLETED_DEPOSIT,
// });
await this.notificationService.sendNotification({ await this.notificationService.sendNotification({
restaurantId: event.restaurantId, restaurantId: event.restaurantId,
@@ -5,14 +5,13 @@ import { EntityManager } from '@mikro-orm/postgresql';
import { Order } from '../../orders/entities/order.entity'; import { Order } from '../../orders/entities/order.entity';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { GatewayManager } from '../gateways/gateway.manager'; import { GatewayManager } from '../gateways/gateway.manager';
import { UserWallet } from 'src/modules/users/entities/user-wallet.entity'; import { WalletTransaction } from 'src/modules/users/entities/wallet-transaction.entity';
import { OrderPaymentContext } from '../interface/payment'; import { OrderPaymentContext } from '../interface/payment';
import { OrderStatus } from 'src/modules/orders/interface/order.interface'; import { OrderStatus } from 'src/modules/orders/interface/order.interface';
import { ChartPeriodEnum, PaymentChartDto } from '../dto/payment-chart.dto'; import { ChartPeriodEnum, PaymentChartDto } from '../dto/payment-chart.dto';
import { PaymentMessage, OrderMessage } from 'src/common/enums/message.enum'; import { PaymentMessage, OrderMessage } from 'src/common/enums/message.enum';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { onlinePaymentSucceedEvent, paymentSucceedEvent } from '../events/payment.events'; import { onlinePaymentSucceedEvent, paymentSucceedEvent } from '../events/payment.events';
import { WalletTransaction } from 'src/modules/users/entities/wallet-transaction.entity';
import { WalletTransactionReason, WalletTransactionType } from 'src/modules/users/interface/wallet'; import { WalletTransactionReason, WalletTransactionType } from 'src/modules/users/interface/wallet';
@Injectable() @Injectable()
@@ -104,20 +103,23 @@ export class PaymentsService {
return; return;
} }
const wallet = await em.findOne(UserWallet, { const walletTransaction = await em.findOne(WalletTransaction, {
user: { id: order.user.id }, user: { id: order.user.id },
restaurant: { id: order.restaurant.id }, restaurant: { id: order.restaurant.id },
}, {
orderBy: { createdAt: 'DESC' }
}); });
if (!wallet) { if (!walletTransaction) {
throw new NotFoundException('User wallet not found'); throw new NotFoundException('User wallet not found');
} }
if (wallet.wallet < ctx.amount) { if (walletTransaction.balance < ctx.amount) {
throw new BadRequestException('Insufficient wallet balance'); throw new BadRequestException('Insufficient wallet balance');
} }
wallet.wallet -= ctx.amount; const newBalance = walletTransaction.balance - ctx.amount;
const payment = await this.getOrCreateLatestPendingPayment(order.id, { const payment = await this.getOrCreateLatestPendingPayment(order.id, {
em, em,
@@ -126,19 +128,20 @@ export class PaymentsService {
gateway: null, gateway: null,
}); });
const walletTransaction = this.em.create(WalletTransaction, { const newWalletTransaction = em.create(WalletTransaction, {
userWallet: wallet, user: order.user,
restaurant: order.restaurant,
amount: ctx.amount, amount: ctx.amount,
type: WalletTransactionType.DEBIT, type: WalletTransactionType.DEBIT,
reason: WalletTransactionReason.ORDER_PAYMENT, reason: WalletTransactionReason.ORDER_PAYMENT,
balance: wallet.wallet - ctx.amount, balance: newBalance,
}); });
payment.status = PaymentStatusEnum.Paid; payment.status = PaymentStatusEnum.Paid;
payment.paidAt = new Date(); payment.paidAt = new Date();
order.status = OrderStatus.PAID; order.status = OrderStatus.PAID;
em.persist([wallet, payment, order, walletTransaction]); em.persist([ payment, order, newWalletTransaction]);
await em.flush(); await em.flush();
}); });
this.eventEmitter.emit( this.eventEmitter.emit(
@@ -1,23 +1,30 @@
import { Entity, Property, ManyToOne, Index, Unique } from '@mikro-orm/core'; import { Entity, Property, ManyToOne, Index, Unique, Enum } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../common/entities/base.entity';
import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity'; import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity';
import { User } from './user.entity'; import { User } from './user.entity';
import { PointTransactionReason } from '../interface/point';
import { PointTransactionType } from '../interface/point';
@Entity({ tableName: 'user_wallets' }) @Entity({ tableName: 'user_wallets' })
@Unique({ properties: ['user', 'restaurant'], name: 'unique_user_restaurant' })
@Index({ properties: ['user', 'restaurant'] }) // Composite index for most common query: find wallet by user and restaurant @Index({ properties: ['user', 'restaurant'] }) // Composite index for most common query: find wallet by user and restaurant
@Index({ properties: ['user'] }) // Index for queries finding all wallets for a user @Index({ properties: ['user'] }) // Index for queries finding all wallets for a user
@Index({ properties: ['restaurant'] }) // Index for queries finding all wallets for a restaurant @Index({ properties: ['restaurant'] }) // Index for queries finding all wallets for a restaurant
export class UserWallet extends BaseEntity { export class PointTransaction extends BaseEntity {
@ManyToOne(() => Restaurant)
restaurant!: Restaurant;
@ManyToOne(() => User) @ManyToOne(() => User)
user!: User; user!: User;
@Property({ default: 0, type: 'int' }) @ManyToOne(() => User)
wallet: number = 0; restaurant!: Restaurant;
@Property({ default: 0, type: 'int' }) @Property({ type: 'decimal', precision: 10, scale: 0 })
points: number = 0; amount!: number;
@Property({ type: 'decimal', precision: 10, scale: 0 })
balance!: number;
@Enum(() => PointTransaction)
type!: PointTransactionType;
@Enum(() => PointTransactionReason)
reason!: PointTransactionReason;
} }
@@ -2,12 +2,18 @@ import { Entity, Index, Property, OneToMany, Collection, Cascade, ManyToOne, Enu
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../common/entities/base.entity';
import { User } from './user.entity'; import { User } from './user.entity';
import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet'; import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet';
import { UserWallet } from './user-wallet.entity'; import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity';
@Entity({ tableName: 'wallet_transactions' }) @Entity({ tableName: 'wallet_transactions' })
@Index({ properties: ['user', 'restaurant'] })
@Index({ properties: ['user'] })
@Index({ properties: ['restaurant'] })
export class WalletTransaction extends BaseEntity { export class WalletTransaction extends BaseEntity {
@ManyToOne(() => UserWallet) @ManyToOne(() => User)
userWallet!: UserWallet; user!: User;
@ManyToOne(() => User)
restaurant!: Restaurant;
@Property({ type: 'decimal', precision: 10, scale: 0 }) @Property({ type: 'decimal', precision: 10, scale: 0 })
amount!: number; amount!: number;
+10
View File
@@ -0,0 +1,10 @@
export enum PointTransactionType {
CREDIT = 'credit',
DEBIT = 'debit',
}
export enum PointTransactionReason {
ORDER_COMPLETED_DEPOSIT = 'order_completed_deposit',
CONVERT_SCORE_TO_POINT = 'convert_score_to_point',
}
-1
View File
@@ -10,5 +10,4 @@ export enum WalletTransactionReason {
CONVERT_SCORE_TO_WALLET = 'convert_score_to_wallet', CONVERT_SCORE_TO_WALLET = 'convert_score_to_wallet',
WITHDRAW = 'withdraw', WITHDRAW = 'withdraw',
DEPOSIT = 'deposit', DEPOSIT = 'deposit',
ORDER_COMPLETED_DEPOSIT = 'order_completed_deposit',
} }
+36 -20
View File
@@ -14,8 +14,7 @@ import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { UserRepository } from '../repositories/user.repository'; import { UserRepository } from '../repositories/user.repository';
import { normalizePhone } from '../../utils/phone.util'; 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 { WalletTransactionRepository } from '../repositories/wallet-transaction.repository';
import { UserWalletRepository } from '../repositories/user-wallet.repository';
import { WalletService } from './wallet.service'; import { WalletService } from './wallet.service';
import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet'; import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet';
import { WalletTransaction } from '../entities/wallet-transaction.entity'; import { WalletTransaction } from '../entities/wallet-transaction.entity';
@@ -25,7 +24,7 @@ export class UserService {
constructor( constructor(
private readonly userRepository: UserRepository, private readonly userRepository: UserRepository,
private readonly restaurantRepository: RestRepository, private readonly restaurantRepository: RestRepository,
private readonly userWalletRepository: UserWalletRepository, private readonly walletTransactionRepository: WalletTransactionRepository,
private readonly walletService: WalletService, private readonly walletService: WalletService,
private readonly em: EntityManager, private readonly em: EntityManager,
) { } ) { }
@@ -75,7 +74,7 @@ export class UserService {
async findById(id: string): Promise<User | null> { async findById(id: string): Promise<User | null> {
return this.userRepository.findOne({ id }); return this.userRepository.findOne({ id });
} }
// todo : transaction code here
async create(phone: string, restId: string): Promise<User> { async create(phone: string, restId: string): Promise<User> {
const normalizedPhone = normalizePhone(phone); const normalizedPhone = normalizePhone(phone);
const _raw = { const _raw = {
@@ -93,8 +92,15 @@ export class UserService {
const createData = _raw as unknown as RequiredEntityData<User>; const createData = _raw as unknown as RequiredEntityData<User>;
const user = this.userRepository.create(createData); const user = this.userRepository.create(createData);
this.userWalletRepository.create({ user: user, restaurant: restaurant, wallet: 0, points: points }); const newWalletTransaction = this.walletTransactionRepository.create({
await this.em.persistAndFlush(user); user: user,
restaurant: restaurant,
balance: 0,
amount: points,
type: WalletTransactionType.CREDIT,
reason: WalletTransactionReason.DEPOSIT
});
await this.em.persistAndFlush([user, newWalletTransaction]);
return user; return user;
} }
@@ -310,13 +316,23 @@ export class UserService {
if (!restaurant) { if (!restaurant) {
throw new NotFoundException(`Restaurant with ID ${restId} not found.`); throw new NotFoundException(`Restaurant with ID ${restId} not found.`);
} }
let userWallet = await em.findOne(UserWallet, { user: { id: userId }, restaurant: { id: restId } }); let walletTransaction = await em.findOne(WalletTransaction, { user: { id: userId }, restaurant: { id: restId } },
if (!userWallet) { { orderBy: { createdAt: 'DESC' } });
userWallet = em.create(UserWallet, { user: user, restaurant, wallet: 0, points: 0 });
await em.persistAndFlush(userWallet); if (!walletTransaction) {
walletTransaction = em.create(WalletTransaction,
{
user: user,
restaurant,
balance: 0,
amount: 0,
type: WalletTransactionType.CREDIT,
reason: WalletTransactionReason.DEPOSIT
});
await em.persistAndFlush(walletTransaction);
} }
// Determine how many points to convert // Determine how many points to convert
const pointsToConvert = userWallet.points; const pointsToConvert = walletTransaction.balance;
// Validate points to convert // Validate points to convert
if (pointsToConvert <= 0) { if (pointsToConvert <= 0) {
@@ -336,18 +352,17 @@ export class UserService {
(Number(pointsToConvert) * Number(restaurant.score.purchaseAmount)) / Number(restaurant.score.purchaseScore); (Number(pointsToConvert) * Number(restaurant.score.purchaseAmount)) / Number(restaurant.score.purchaseScore);
// Convert points to wallet (1:1 ratio - can be made configurable) // Convert points to wallet (1:1 ratio - can be made configurable)
const walletTransaction = em.create(WalletTransaction, { const newWalletTransaction = em.create(WalletTransaction, {
userWallet: userWallet, user: user,
restaurant: restaurant,
amount: walletIncreaseAmount, amount: walletIncreaseAmount,
type: WalletTransactionType.CREDIT, type: WalletTransactionType.CREDIT,
reason: WalletTransactionReason.CONVERT_SCORE_TO_WALLET, reason: WalletTransactionReason.CONVERT_SCORE_TO_WALLET,
balance: userWallet.wallet + walletIncreaseAmount, balance: walletTransaction.balance + walletIncreaseAmount,
}); });
// Update user's wallet and points // Update user's wallet and points
userWallet.wallet = (userWallet.wallet || 0) + walletIncreaseAmount; walletTransaction.balance = walletTransaction.balance + walletIncreaseAmount;
userWallet.points = (userWallet.points || 0) - pointsToConvert; em.persist([newWalletTransaction]);
em.persist([userWallet, walletTransaction]);
await em.flush(); await em.flush();
@@ -355,8 +370,9 @@ export class UserService {
}); });
} }
getUserWallet(userId: string, restId: string): Promise<UserWallet | null> { getUserWallet(userId: string, restId: string): Promise<WalletTransaction | null> {
return this.walletService.getUserWallet(userId, restId); return this.walletTransactionRepository.findOne({ user: { id: userId }, restaurant: { id: restId } },
{ orderBy: { createdAt: 'DESC' } });
} }
getUserWalletTransactions(userId: string, restId: string, dto: FindWalletTransactionsDto) { getUserWalletTransactions(userId: string, restId: string, dto: FindWalletTransactionsDto) {
+17 -18
View File
@@ -2,9 +2,7 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/comm
import { FilterQuery } from '@mikro-orm/core'; import { FilterQuery } from '@mikro-orm/core';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { WalletTransaction } from '../entities/wallet-transaction.entity'; import { WalletTransaction } from '../entities/wallet-transaction.entity';
import { UserWallet } from '../entities/user-wallet.entity';
import { WalletTransactionRepository } from '../repositories/wallet-transaction.repository'; import { WalletTransactionRepository } from '../repositories/wallet-transaction.repository';
import { UserWalletRepository } from '../repositories/user-wallet.repository';
import { FindWalletTransactionsDto } from '../dto/find-wallet-transactions.dto'; import { FindWalletTransactionsDto } from '../dto/find-wallet-transactions.dto';
import { CreateWalletTransactionDto } from '../dto/create-wallet-transaction.dto'; import { CreateWalletTransactionDto } from '../dto/create-wallet-transaction.dto';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
@@ -14,7 +12,6 @@ import { WalletTransactionType } from '../interface/wallet';
export class WalletService { export class WalletService {
constructor( constructor(
private readonly walletTransactionRepository: WalletTransactionRepository, private readonly walletTransactionRepository: WalletTransactionRepository,
private readonly userWalletRepository: UserWalletRepository,
private readonly em: EntityManager, private readonly em: EntityManager,
) { } ) { }
@@ -40,18 +37,19 @@ export class WalletService {
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
// Find the user's wallet for this restaurant // Find the user's wallet for this restaurant
const userWallet = await this.userWalletRepository.findOne({ const walletTransaction = await this.walletTransactionRepository.findOne({
user: { id: userId }, user: { id: userId },
restaurant: { id: restId }, restaurant: { id: restId },
}); });
if (!userWallet) { if (!walletTransaction) {
throw new NotFoundException(`User wallet not found for user ${userId} and restaurant ${restId}.`); throw new NotFoundException(`User wallet not found for user ${userId} and restaurant ${restId}.`);
} }
// Build the 'where' filter query // Build the 'where' filter query
const where: FilterQuery<WalletTransaction> = { const where: FilterQuery<WalletTransaction> = {
userWallet: { id: userWallet.id }, user: { id: userId },
restaurant: { id: restId },
}; };
// Add type filter // Add type filter
@@ -108,8 +106,10 @@ export class WalletService {
}; };
} }
getUserWallet(userId: string, restId: string): Promise<UserWallet | null> { getUserWallet(userId: string, restId: string): Promise<WalletTransaction | null> {
return this.userWalletRepository.findOne({ user: { id: userId }, restaurant: { id: restId } }); return this.walletTransactionRepository.findOne({ user: { id: userId },
restaurant: { id: restId } },
{ orderBy: { createdAt: 'DESC' } });
} }
async createTransaction( async createTransaction(
@@ -126,17 +126,17 @@ export class WalletService {
} }
// Find the user's wallet for this restaurant // Find the user's wallet for this restaurant
const userWallet = await em.findOne(UserWallet, { const walletTransaction = await em.findOne(WalletTransaction, {
user: { id: userId }, user: { id: userId },
restaurant: { id: restId }, restaurant: { id: restId },
}); });
if (!userWallet) { if (!walletTransaction) {
throw new NotFoundException(`User wallet not found for user ${userId} and restaurant ${restId}.`); throw new NotFoundException(`User wallet not found for user ${userId} and restaurant ${restId}.`);
} }
// Calculate new balance // Calculate new balance
const currentBalance = userWallet.wallet || 0; const currentBalance = walletTransaction.balance || 0;
let newBalance: number; let newBalance: number;
if (type === WalletTransactionType.CREDIT) { if (type === WalletTransactionType.CREDIT) {
@@ -153,20 +153,19 @@ export class WalletService {
} }
// Update wallet balance // Update wallet balance
userWallet.wallet = newBalance; walletTransaction.balance = newBalance;
await em.persistAndFlush(userWallet); await em.persistAndFlush(walletTransaction);
// Create transaction record // Create transaction record
const transaction = em.create(WalletTransaction, { const transaction = em.create(WalletTransaction, {
userWallet, user: walletTransaction.user,
restaurant: walletTransaction.restaurant,
amount, amount,
balance: newBalance,
type, type,
reason, reason,
balance: newBalance,
}); });
await em.persistAndFlush([walletTransaction, transaction]);
await em.persistAndFlush(transaction);
return transaction; return transaction;
} }
} }
@@ -0,0 +1,17 @@
import { Injectable } from '@nestjs/common';
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
import { PointTransaction } from '../entities/point-transaction.entity';
@Injectable()
export class PointTransactionRepository extends EntityRepository<PointTransaction> {
constructor(readonly em: EntityManager) {
super(em, PointTransaction);
}
async getcurrentPointBalance(userId: string, restaurantId: string): Promise<number> {
const pointTransaction = await this.em.findOne(PointTransaction, {
user: { id: userId },
restaurant: { id: restaurantId },
});
return pointTransaction?.balance || 0;
}
}
@@ -1,10 +0,0 @@
import { Injectable } from '@nestjs/common';
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
import { UserWallet } from '../entities/user-wallet.entity';
@Injectable()
export class UserWalletRepository extends EntityRepository<UserWallet> {
constructor(readonly em: EntityManager) {
super(em, UserWallet);
}
}
@@ -7,4 +7,11 @@ export class WalletTransactionRepository extends EntityRepository<WalletTransact
constructor(readonly em: EntityManager) { constructor(readonly em: EntityManager) {
super(em, WalletTransaction); super(em, WalletTransaction);
} }
async getCurrentWalletBalance(userId: string, restaurantId: string): Promise<number> {
const walletTransaction = await this.em.findOne(WalletTransaction, {
user: { id: userId },
restaurant: { id: restaurantId },
}, { orderBy: { createdAt: 'desc' } });
return walletTransaction?.balance || 0;
}
} }
+10 -6
View File
@@ -7,17 +7,21 @@ import { UserAddress } from './entities/user-address.entity';
import { JwtModule } from '@nestjs/jwt'; 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 { WalletTransactionRepository } from './repositories/wallet-transaction.repository'; import { WalletTransactionRepository } from './repositories/wallet-transaction.repository';
import { WalletService } from './providers/wallet.service'; import { WalletService } from './providers/wallet.service';
import { UserWallet } from './entities/user-wallet.entity';
import { WalletTransaction } from './entities/wallet-transaction.entity'; import { WalletTransaction } from './entities/wallet-transaction.entity';
import { PointTransaction } from './entities/point-transaction.entity';
@Module({ @Module({
providers: [UserService, WalletService, UserRepository, UserWalletRepository, WalletTransactionRepository], providers: [UserService, WalletService, UserRepository, WalletTransactionRepository],
controllers: [UsersController], controllers: [UsersController],
imports: [MikroOrmModule.forFeature([User, UserAddress, UserWallet, imports: [MikroOrmModule.forFeature([
WalletTransaction]), JwtModule, RestaurantsModule], User,
exports: [UserService, WalletService, UserRepository, UserWalletRepository, WalletTransactionRepository], UserAddress,
WalletTransaction,
PointTransaction
]),
JwtModule, RestaurantsModule, WalletTransactionRepository],
exports: [UserService, WalletService, UserRepository, WalletTransactionRepository],
}) })
export class UserModule { } export class UserModule { }
+6 -6
View File
@@ -12,27 +12,27 @@ export const adminsData: AdminData[] = [
firstName: 'مرتضی', firstName: 'مرتضی',
lastName: 'مرتضایی', lastName: 'مرتضایی',
roleName: 'مدیر ( پلن ویژه)', roleName: 'مدیر ( پلن ویژه)',
restaurantSlug: 'zhivan', restaurantSlug: 'boote',
}, },
{ {
phone: '09185290775', phone: '09185290775',
firstName: 'حمید', firstName: 'حمید',
lastName: 'ضرقامی', lastName: 'ضرقامی',
roleName: 'مدیر (پلن پایه)', roleName: 'مدیر ( پلن ویژه)',
restaurantSlug: 'boote', restaurantSlug: 'boote',
}, },
{ {
phone: '09129283395', phone: '09129283395',
firstName: 'مهرداد', firstName: 'مهرداد',
lastName: 'مظفری', lastName: 'مظفری',
roleName: 'superAdmin', roleName: 'مدیر ( پلن ویژه)',
restaurantSlug: null, restaurantSlug: 'boote',
}, },
{ {
phone: '09184317567', phone: '09184317567',
firstName: 'ادمین', firstName: 'ادمین',
lastName: 'هنر', lastName: 'هنر',
roleName: 'مدیر (پلن پایه)', roleName: 'مدیر ( پلن ویژه)',
restaurantSlug: 'honar', restaurantSlug: 'boote',
}, },
]; ];
+1 -1
View File
@@ -119,7 +119,7 @@ export const restaurantsData: RestaurantData[] = [
marriageDateScore: '2', marriageDateScore: '2',
referrerScore: '2', referrerScore: '2',
}, },
plan: PlanEnum.Base, plan: PlanEnum.Premium,
subscriptionId: 'sub_seed_boote_001', subscriptionId: 'sub_seed_boote_001',
}, },
// { // {
+8 -5
View File
@@ -1,7 +1,8 @@
import type { EntityManager } from '@mikro-orm/core'; import type { EntityManager } from '@mikro-orm/core';
import { User } from '../modules/users/entities/user.entity'; import { User } from '../modules/users/entities/user.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity'; import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { UserWallet } from '../modules/users/entities/user-wallet.entity'; import { WalletTransaction } from '../modules/users/entities/wallet-transaction.entity';
import { WalletTransactionReason, WalletTransactionType } from 'src/modules/users/interface/wallet';
export class UserWalletsSeeder { export class UserWalletsSeeder {
async run(em: EntityManager): Promise<void> { async run(em: EntityManager): Promise<void> {
@@ -11,17 +12,19 @@ export class UserWalletsSeeder {
for (const user of users) { for (const user of users) {
for (const restaurant of restaurants) { for (const restaurant of restaurants) {
// Check if wallet already exists for this user-restaurant combination // Check if wallet already exists for this user-restaurant combination
const existingWallet = await em.findOne(UserWallet, { const existingWallet = await em.findOne(WalletTransaction, {
user: { id: user.id }, user: { id: user.id },
restaurant: { id: restaurant.id }, restaurant: { id: restaurant.id },
}); });
if (!existingWallet) { if (!existingWallet) {
const wallet = em.create(UserWallet, { const wallet = em.create(WalletTransaction, {
user, user,
restaurant, restaurant,
wallet: 150000000, balance: 150000000,
points: 1250, amount: 1250,
type: WalletTransactionType.CREDIT,
reason: WalletTransactionReason.DEPOSIT,
}); });
em.persist(wallet); em.persist(wallet);
} }