rerfactor user wallet ans user points
This commit is contained in:
+17
-10
@@ -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 { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity';
|
||||
import { User } from './user.entity';
|
||||
import { PointTransactionReason } from '../interface/point';
|
||||
import { PointTransactionType } from '../interface/point';
|
||||
|
||||
@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'] }) // Index for queries finding all wallets for a user
|
||||
@Index({ properties: ['restaurant'] }) // Index for queries finding all wallets for a restaurant
|
||||
export class UserWallet extends BaseEntity {
|
||||
@ManyToOne(() => Restaurant)
|
||||
restaurant!: Restaurant;
|
||||
|
||||
export class PointTransaction extends BaseEntity {
|
||||
@ManyToOne(() => User)
|
||||
user!: User;
|
||||
|
||||
@Property({ default: 0, type: 'int' })
|
||||
wallet: number = 0;
|
||||
@ManyToOne(() => User)
|
||||
restaurant!: Restaurant;
|
||||
|
||||
@Property({ default: 0, type: 'int' })
|
||||
points: number = 0;
|
||||
@Property({ type: 'decimal', precision: 10, scale: 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 { User } from './user.entity';
|
||||
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' })
|
||||
@Index({ properties: ['user', 'restaurant'] })
|
||||
@Index({ properties: ['user'] })
|
||||
@Index({ properties: ['restaurant'] })
|
||||
export class WalletTransaction extends BaseEntity {
|
||||
@ManyToOne(() => UserWallet)
|
||||
userWallet!: UserWallet;
|
||||
@ManyToOne(() => User)
|
||||
user!: User;
|
||||
|
||||
@ManyToOne(() => User)
|
||||
restaurant!: Restaurant;
|
||||
|
||||
@Property({ type: 'decimal', precision: 10, scale: 0 })
|
||||
amount!: number;
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
@@ -10,5 +10,4 @@ export enum WalletTransactionReason {
|
||||
CONVERT_SCORE_TO_WALLET = 'convert_score_to_wallet',
|
||||
WITHDRAW = 'withdraw',
|
||||
DEPOSIT = 'deposit',
|
||||
ORDER_COMPLETED_DEPOSIT = 'order_completed_deposit',
|
||||
}
|
||||
@@ -14,8 +14,7 @@ import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||
import { UserRepository } from '../repositories/user.repository';
|
||||
import { normalizePhone } from '../../utils/phone.util';
|
||||
import { RestRepository } from 'src/modules/restaurants/repositories/rest.repository';
|
||||
import { UserWallet } from '../entities/user-wallet.entity';
|
||||
import { UserWalletRepository } from '../repositories/user-wallet.repository';
|
||||
import { WalletTransactionRepository } from '../repositories/wallet-transaction.repository';
|
||||
import { WalletService } from './wallet.service';
|
||||
import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet';
|
||||
import { WalletTransaction } from '../entities/wallet-transaction.entity';
|
||||
@@ -25,7 +24,7 @@ export class UserService {
|
||||
constructor(
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly restaurantRepository: RestRepository,
|
||||
private readonly userWalletRepository: UserWalletRepository,
|
||||
private readonly walletTransactionRepository: WalletTransactionRepository,
|
||||
private readonly walletService: WalletService,
|
||||
private readonly em: EntityManager,
|
||||
) { }
|
||||
@@ -75,7 +74,7 @@ export class UserService {
|
||||
async findById(id: string): Promise<User | null> {
|
||||
return this.userRepository.findOne({ id });
|
||||
}
|
||||
|
||||
// todo : transaction code here
|
||||
async create(phone: string, restId: string): Promise<User> {
|
||||
const normalizedPhone = normalizePhone(phone);
|
||||
const _raw = {
|
||||
@@ -93,8 +92,15 @@ export class UserService {
|
||||
const createData = _raw as unknown as RequiredEntityData<User>;
|
||||
|
||||
const user = this.userRepository.create(createData);
|
||||
this.userWalletRepository.create({ user: user, restaurant: restaurant, wallet: 0, points: points });
|
||||
await this.em.persistAndFlush(user);
|
||||
const newWalletTransaction = this.walletTransactionRepository.create({
|
||||
user: user,
|
||||
restaurant: restaurant,
|
||||
balance: 0,
|
||||
amount: points,
|
||||
type: WalletTransactionType.CREDIT,
|
||||
reason: WalletTransactionReason.DEPOSIT
|
||||
});
|
||||
await this.em.persistAndFlush([user, newWalletTransaction]);
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -310,13 +316,23 @@ export class UserService {
|
||||
if (!restaurant) {
|
||||
throw new NotFoundException(`Restaurant with ID ${restId} not found.`);
|
||||
}
|
||||
let userWallet = await em.findOne(UserWallet, { user: { id: userId }, restaurant: { id: restId } });
|
||||
if (!userWallet) {
|
||||
userWallet = em.create(UserWallet, { user: user, restaurant, wallet: 0, points: 0 });
|
||||
await em.persistAndFlush(userWallet);
|
||||
let walletTransaction = await em.findOne(WalletTransaction, { user: { id: userId }, restaurant: { id: restId } },
|
||||
{ orderBy: { createdAt: 'DESC' } });
|
||||
|
||||
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
|
||||
const pointsToConvert = userWallet.points;
|
||||
const pointsToConvert = walletTransaction.balance;
|
||||
|
||||
// Validate points to convert
|
||||
if (pointsToConvert <= 0) {
|
||||
@@ -336,18 +352,17 @@ export class UserService {
|
||||
(Number(pointsToConvert) * Number(restaurant.score.purchaseAmount)) / Number(restaurant.score.purchaseScore);
|
||||
// Convert points to wallet (1:1 ratio - can be made configurable)
|
||||
|
||||
const walletTransaction = em.create(WalletTransaction, {
|
||||
userWallet: userWallet,
|
||||
const newWalletTransaction = em.create(WalletTransaction, {
|
||||
user: user,
|
||||
restaurant: restaurant,
|
||||
amount: walletIncreaseAmount,
|
||||
type: WalletTransactionType.CREDIT,
|
||||
reason: WalletTransactionReason.CONVERT_SCORE_TO_WALLET,
|
||||
balance: userWallet.wallet + walletIncreaseAmount,
|
||||
balance: walletTransaction.balance + walletIncreaseAmount,
|
||||
});
|
||||
// Update user's wallet and points
|
||||
userWallet.wallet = (userWallet.wallet || 0) + walletIncreaseAmount;
|
||||
userWallet.points = (userWallet.points || 0) - pointsToConvert;
|
||||
|
||||
em.persist([userWallet, walletTransaction]);
|
||||
walletTransaction.balance = walletTransaction.balance + walletIncreaseAmount;
|
||||
em.persist([newWalletTransaction]);
|
||||
|
||||
await em.flush();
|
||||
|
||||
@@ -355,8 +370,9 @@ export class UserService {
|
||||
});
|
||||
}
|
||||
|
||||
getUserWallet(userId: string, restId: string): Promise<UserWallet | null> {
|
||||
return this.walletService.getUserWallet(userId, restId);
|
||||
getUserWallet(userId: string, restId: string): Promise<WalletTransaction | null> {
|
||||
return this.walletTransactionRepository.findOne({ user: { id: userId }, restaurant: { id: restId } },
|
||||
{ orderBy: { createdAt: 'DESC' } });
|
||||
}
|
||||
|
||||
getUserWalletTransactions(userId: string, restId: string, dto: FindWalletTransactionsDto) {
|
||||
|
||||
@@ -2,9 +2,7 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/comm
|
||||
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';
|
||||
@@ -14,7 +12,6 @@ import { WalletTransactionType } from '../interface/wallet';
|
||||
export class WalletService {
|
||||
constructor(
|
||||
private readonly walletTransactionRepository: WalletTransactionRepository,
|
||||
private readonly userWalletRepository: UserWalletRepository,
|
||||
private readonly em: EntityManager,
|
||||
) { }
|
||||
|
||||
@@ -40,18 +37,19 @@ export class WalletService {
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// Find the user's wallet for this restaurant
|
||||
const userWallet = await this.userWalletRepository.findOne({
|
||||
const walletTransaction = await this.walletTransactionRepository.findOne({
|
||||
user: { id: userId },
|
||||
restaurant: { id: restId },
|
||||
});
|
||||
|
||||
if (!userWallet) {
|
||||
if (!walletTransaction) {
|
||||
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 },
|
||||
user: { id: userId },
|
||||
restaurant: { id: restId },
|
||||
};
|
||||
|
||||
// Add type filter
|
||||
@@ -108,8 +106,10 @@ export class WalletService {
|
||||
};
|
||||
}
|
||||
|
||||
getUserWallet(userId: string, restId: string): Promise<UserWallet | null> {
|
||||
return this.userWalletRepository.findOne({ user: { id: userId }, restaurant: { id: restId } });
|
||||
getUserWallet(userId: string, restId: string): Promise<WalletTransaction | null> {
|
||||
return this.walletTransactionRepository.findOne({ user: { id: userId },
|
||||
restaurant: { id: restId } },
|
||||
{ orderBy: { createdAt: 'DESC' } });
|
||||
}
|
||||
|
||||
async createTransaction(
|
||||
@@ -126,17 +126,17 @@ export class WalletService {
|
||||
}
|
||||
|
||||
// Find the user's wallet for this restaurant
|
||||
const userWallet = await em.findOne(UserWallet, {
|
||||
const walletTransaction = await em.findOne(WalletTransaction, {
|
||||
user: { id: userId },
|
||||
restaurant: { id: restId },
|
||||
});
|
||||
|
||||
if (!userWallet) {
|
||||
if (!walletTransaction) {
|
||||
throw new NotFoundException(`User wallet not found for user ${userId} and restaurant ${restId}.`);
|
||||
}
|
||||
|
||||
// Calculate new balance
|
||||
const currentBalance = userWallet.wallet || 0;
|
||||
const currentBalance = walletTransaction.balance || 0;
|
||||
let newBalance: number;
|
||||
|
||||
if (type === WalletTransactionType.CREDIT) {
|
||||
@@ -153,20 +153,19 @@ export class WalletService {
|
||||
}
|
||||
|
||||
// Update wallet balance
|
||||
userWallet.wallet = newBalance;
|
||||
await em.persistAndFlush(userWallet);
|
||||
walletTransaction.balance = newBalance;
|
||||
await em.persistAndFlush(walletTransaction);
|
||||
|
||||
// Create transaction record
|
||||
const transaction = em.create(WalletTransaction, {
|
||||
userWallet,
|
||||
user: walletTransaction.user,
|
||||
restaurant: walletTransaction.restaurant,
|
||||
amount,
|
||||
balance: newBalance,
|
||||
type,
|
||||
reason,
|
||||
balance: newBalance,
|
||||
});
|
||||
|
||||
await em.persistAndFlush(transaction);
|
||||
|
||||
await em.persistAndFlush([walletTransaction, 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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,21 @@ import { UserAddress } from './entities/user-address.entity';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { UserRepository } from './repositories/user.repository';
|
||||
import { RestaurantsModule } from '../restaurants/restaurants.module';
|
||||
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 { WalletTransaction } from './entities/wallet-transaction.entity';
|
||||
import { PointTransaction } from './entities/point-transaction.entity';
|
||||
|
||||
@Module({
|
||||
providers: [UserService, WalletService, UserRepository, UserWalletRepository, WalletTransactionRepository],
|
||||
providers: [UserService, WalletService, UserRepository, WalletTransactionRepository],
|
||||
controllers: [UsersController],
|
||||
imports: [MikroOrmModule.forFeature([User, UserAddress, UserWallet,
|
||||
WalletTransaction]), JwtModule, RestaurantsModule],
|
||||
exports: [UserService, WalletService, UserRepository, UserWalletRepository, WalletTransactionRepository],
|
||||
imports: [MikroOrmModule.forFeature([
|
||||
User,
|
||||
UserAddress,
|
||||
WalletTransaction,
|
||||
PointTransaction
|
||||
]),
|
||||
JwtModule, RestaurantsModule, WalletTransactionRepository],
|
||||
exports: [UserService, WalletService, UserRepository, WalletTransactionRepository],
|
||||
})
|
||||
export class UserModule { }
|
||||
|
||||
Reference in New Issue
Block a user