rerfactor user wallet ans user points
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
+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 { 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;
|
||||||
|
|||||||
@@ -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',
|
CONVERT_SCORE_TO_WALLET = 'convert_score_to_wallet',
|
||||||
WITHDRAW = 'withdraw',
|
WITHDRAW = 'withdraw',
|
||||||
DEPOSIT = 'deposit',
|
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 { 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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { }
|
||||||
|
|||||||
@@ -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',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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',
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user