This commit is contained in:
2026-04-16 12:57:03 +03:30
parent 5ae39bd082
commit b369794d42
+191 -119
View File
@@ -30,238 +30,293 @@ export class ResellerService {
private readonly userBankAccountRepository: UserBankAccountRepository, private readonly userBankAccountRepository: UserBankAccountRepository,
private readonly walletsService: WalletsService, private readonly walletsService: WalletsService,
private readonly dataSource: DataSource, private readonly dataSource: DataSource,
) { } ) {}
// ---------------------------------------------------------------------------
// Create a reseller record for an existing user.
// ---------------------------------------------------------------------------
async create(dto: CreateResellerDto) { async create(dto: CreateResellerDto) {
const { name, phone } = dto const { name, phone } = dto;
const user = await this.usersService.findOneWithPhone(phone) const user = await this.usersService.findOneWithPhone(phone);
if (!user) { if (!user) {
throw new BadRequestException(AuthMessage.USER_NOT_FOUND) throw new BadRequestException(AuthMessage.USER_NOT_FOUND);
} }
const reseller = this.resellerRepository.create({ const reseller = this.resellerRepository.create({
name, name,
owner: user owner: user,
}) });
await this.resellerRepository.save(reseller) await this.resellerRepository.save(reseller);
return reseller return reseller;
} }
// ---------------------------------------------------------------------------
// Return all resellers with their owners.
// ---------------------------------------------------------------------------
async findAll() { async findAll() {
return this.resellerRepository.find({ return this.resellerRepository.find({
relations: { relations: {
owner: true owner: true,
} },
}) });
} }
// ---------------------------------------------------------------------------
// Return agents assigned to the reseller owner.
// ---------------------------------------------------------------------------
async finResellerAgents(userId: string) { async finResellerAgents(userId: string) {
const reseller = await this.FindOneOrFailByUserId(userId) const reseller = await this.FindOneOrFailByUserId(userId);
return reseller.agents return reseller.agents;
} }
// ---------------------------------------------------------------------------
// Find a reseller by owner user id and fail if it does not exist.
// ---------------------------------------------------------------------------
async FindOneOrFailByUserId(userId: string) { async FindOneOrFailByUserId(userId: string) {
const reseller = await this.resellerRepository.findOne({ const reseller = await this.resellerRepository.findOne({
where: { owner: { id: userId } }, where: { owner: { id: userId } },
relations: { agents: true } relations: { agents: true },
}) });
if (!reseller) { if (!reseller) {
throw new NotFoundException() throw new NotFoundException();
} }
return reseller return reseller;
} }
// ---------------------------------------------------------------------------
// Attach an existing user to the reseller as an agent.
// ---------------------------------------------------------------------------
async createResellerAgent(userId: string, userTobeAgentPhone: string) { async createResellerAgent(userId: string, userTobeAgentPhone: string) {
const reseller = await this.FindOneOrFailByUserId(userId) const reseller = await this.FindOneOrFailByUserId(userId);
const user = await this.usersService.findOneWithPhone(userTobeAgentPhone) const user = await this.usersService.findOneWithPhone(userTobeAgentPhone);
if (!user) { if (!user) {
throw new NotFoundException(AuthMessage.USER_NOT_FOUND) throw new NotFoundException(AuthMessage.USER_NOT_FOUND);
} }
user.reseller = reseller user.reseller = reseller;
await this.usersService.save(user) await this.usersService.save(user);
return user return user;
} }
// ---------------------------------------------------------------------------
// Remove an agent from the reseller if it belongs to them.
// ---------------------------------------------------------------------------
async removeResellerAgent(userId: string, agentId: string) { async removeResellerAgent(userId: string, agentId: string) {
const reseller = await this.FindOneOrFailByUserId(userId) const reseller = await this.FindOneOrFailByUserId(userId);
const { user } = await this.usersService.findOneById(agentId) const { user } = await this.usersService.findOneById(agentId);
if (!user) { if (!user) {
throw new NotFoundException(AuthMessage.USER_NOT_FOUND) throw new NotFoundException(AuthMessage.USER_NOT_FOUND);
} }
if (user.reseller?.id !== reseller.id) { if (user.reseller?.id !== reseller.id) {
throw new BadRequestException("بازاریاب به این نمایندگی تعلق ندارد") throw new BadRequestException('بازاریاب به این نمایندگی تعلق ندارد');
} }
user.reseller = undefined user.reseller = undefined;
await this.usersService.save(user) await this.usersService.save(user);
return user return user;
} }
// ---------------------------------------------------------------------------
// Return customers referred by all agents of the reseller.
// ---------------------------------------------------------------------------
async findResellerCustomers(userId: string, query: SearchCustomersQueryDto) { async findResellerCustomers(userId: string, query: SearchCustomersQueryDto) {
const reselller = await this.FindOneOrFailByUserId(userId) const reseller = await this.FindOneOrFailByUserId(userId);
const agentIds = reselller.agents.map(ag => ag.id) const agentIds = reseller.agents.map((agent) => agent.id);
if (!agentIds.length) { if (!agentIds.length) {
return { customers: [], count: 0, paginate: true }; return { customers: [], count: 0, paginate: true };
} }
return this.referralService.findPaginatedReferedUsers(agentIds, query) return this.referralService.findPaginatedReferedUsers(agentIds, query);
} }
// *************** Invoice *************** // ---------------------------------------------------------------------------
// Resolve invoice lookup based on reseller or agent role.
// ---------------------------------------------------------------------------
async findInvoices(userId: string, type: ResellerType, query: SearchResellerInvoicesQueryDto) { async findInvoices(userId: string, type: ResellerType, query: SearchResellerInvoicesQueryDto) {
if (type == ResellerType.agent) { if (type === ResellerType.agent) {
return this.findAgentCustomersInvoices(userId, query) return this.findAgentCustomersInvoices(userId, query);
} else if (type == ResellerType.reseller) { }
return this.findResellerCustomersInvoices(userId, query)
if (type === ResellerType.reseller) {
return this.findResellerCustomersInvoices(userId, query);
} }
} }
// ---------------------------------------------------------------------------
// Return invoices for customers referred by the reseller's agents.
// ---------------------------------------------------------------------------
private async findResellerCustomersInvoices(userId: string, query: SearchResellerInvoicesQueryDto) { private async findResellerCustomersInvoices(userId: string, query: SearchResellerInvoicesQueryDto) {
const reselller = await this.FindOneOrFailByUserId(userId) const reseller = await this.FindOneOrFailByUserId(userId);
const agentIds = reselller.agents.map(ag => ag.id) const agentIds = reseller.agents.map((agent) => agent.id);
if (!agentIds.length) { if (!agentIds.length) {
return { invoices: [], count: 0, paginate: true }; return { invoices: [], count: 0, paginate: true };
} }
const customerIds = await this.referralService.findReferedUsersIds(agentIds) const customerIds = await this.referralService.findReferedUsersIds(agentIds);
const [invoices, count] = await this.invoicesService.findInvoicesByCustomerIds(customerIds, query) const [invoices, count] = await this.invoicesService.findInvoicesByCustomerIds(customerIds, query);
return { invoices, count, paginate: true }; return { invoices, count, paginate: true };
} }
// ---------------------------------------------------------------------------
// Return invoices for customers referred by a single agent.
// ---------------------------------------------------------------------------
private async findAgentCustomersInvoices(userId: string, query: SearchResellerInvoicesQueryDto) { private async findAgentCustomersInvoices(userId: string, query: SearchResellerInvoicesQueryDto) {
const agentIds = [userId];
const agentIds = [userId] const customerIds = await this.referralService.findReferedUsersIds(agentIds);
const customerIds = await this.referralService.findReferedUsersIds(agentIds) const [invoices, count] = await this.invoicesService.findInvoicesByCustomerIds(customerIds, query);
const [invoices, count] = await this.invoicesService.findInvoicesByCustomerIds(customerIds, query)
return { invoices, count, paginate: true }; return { invoices, count, paginate: true };
} }
// *********** Rewards ********************* // ---------------------------------------------------------------------------
// Resolve reward lookup based on reseller or agent role.
// ---------------------------------------------------------------------------
async findRewards(userId: string, type: ResellerType, query: SearchWithdrawRequestQueryDto) { async findRewards(userId: string, type: ResellerType, query: SearchWithdrawRequestQueryDto) {
if (type == ResellerType.agent) { if (type === ResellerType.agent) {
return this.findAgentRewards(userId, query) return this.findAgentRewards(userId, query);
} else if (type == ResellerType.reseller) { }
return this.findResellerAgentsRewards(userId, query)
if (type === ResellerType.reseller) {
return this.findResellerAgentsRewards(userId, query);
} }
} }
// ---------------------------------------------------------------------------
// Return rewards earned by all agents under a reseller.
// ---------------------------------------------------------------------------
async findResellerAgentsRewards(userId: string, query: SearchWithdrawRequestQueryDto) { async findResellerAgentsRewards(userId: string, query: SearchWithdrawRequestQueryDto) {
const reselller = await this.FindOneOrFailByUserId(userId) const reseller = await this.FindOneOrFailByUserId(userId);
const agentIds = reselller.agents.map(ag => ag.id) const agentIds = reseller.agents.map((agent) => agent.id);
if (!agentIds.length) { if (!agentIds.length) {
return { rewards: [], count: 0, paginate: true }; return { rewards: [], count: 0, paginate: true };
} }
return this.referralService.findRewards(agentIds, query) return this.referralService.findRewards(agentIds, query);
} }
// ---------------------------------------------------------------------------
// Return rewards earned by a single agent.
// ---------------------------------------------------------------------------
async findAgentRewards(userId: string, query: SearchWithdrawRequestQueryDto) { async findAgentRewards(userId: string, query: SearchWithdrawRequestQueryDto) {
return this.referralService.findRewards([userId], query) return this.referralService.findRewards([userId], query);
} }
//************* withdraw request ***********/ // ---------------------------------------------------------------------------
// Return paginated withdraw requests for admin review.
// ---------------------------------------------------------------------------
async getwithdrawRequestsForAdmin(queryDto: SearchWithdrawRequestQueryDto) { async getwithdrawRequestsForAdmin(queryDto: SearchWithdrawRequestQueryDto) {
const [requests, count] = await this.withdrawRequestRepository.findPaginatedListForAdmin(queryDto); const [requests, count] = await this.withdrawRequestRepository.findPaginatedListForAdmin(queryDto);
return { requests, count, paginate: true }; return { requests, count, paginate: true };
} }
// ---------------------------------------------------------------------------
// Return paginated withdraw requests for a specific user.
// ---------------------------------------------------------------------------
async getWithdrawRequests(userId: string, queryDto: SearchWithdrawRequestQueryDto) { async getWithdrawRequests(userId: string, queryDto: SearchWithdrawRequestQueryDto) {
const [requests, count] = await this.withdrawRequestRepository.findPaginatedList(userId, queryDto); const [requests, count] = await this.withdrawRequestRepository.findPaginatedList(userId, queryDto);
return { requests, count, paginate: true }; return { requests, count, paginate: true };
} }
// ---------------------------------------------------------------------------
// Create a withdraw request after validating balance and pending requests.
// ---------------------------------------------------------------------------
async createWithdrawRequest(userId: string, requestedAmount: number) { async createWithdrawRequest(userId: string, requestedAmount: number) {
const { user } = await this.usersService.findOneById(userId) const { user } = await this.usersService.findOneById(userId);
if (!user) { if (!user) {
throw new NotFoundException(AuthMessage.USER_NOT_FOUND) throw new NotFoundException(AuthMessage.USER_NOT_FOUND);
} }
const pendingWithdrawRequest = await this.withdrawRequestRepository.findOne({ const pendingWithdrawRequest = await this.withdrawRequestRepository.findOne({
where: { where: {
user: { id: userId }, user: { id: userId },
paidAt: IsNull() paidAt: IsNull(),
} },
}) });
if (pendingWithdrawRequest) { if (pendingWithdrawRequest) {
throw new BadRequestException("امکان ایجاد درخواست برداشت جدید نیست") throw new BadRequestException('امکان ایجاد درخواست برداشت جدید نیست');
} }
const { balance } = await this.walletsService.getBalance(userId) const { balance } = await this.walletsService.getBalance(userId);
if (new Decimal(balance).lessThan(requestedAmount)) { if (new Decimal(balance).lessThan(requestedAmount)) {
throw new BadRequestException("برداشت بیشتر از موجودی ممکن نیست") throw new BadRequestException('برداشت بیشتر از موجودی ممکن نیست');
} }
const request = this.withdrawRequestRepository.create({ const request = this.withdrawRequestRepository.create({
requestedAmount, requestedAmount,
user user,
}) });
return this.withdrawRequestRepository.save(request) return this.withdrawRequestRepository.save(request);
} }
// ---------------------------------------------------------------------------
// Delete a pending withdraw request owned by the user.
// ---------------------------------------------------------------------------
async removeWithdrawRequest(userId: string, withdrawRequestId: string) { async removeWithdrawRequest(userId: string, withdrawRequestId: string) {
const request = await this.withdrawRequestRepository.findOne({ const request = await this.withdrawRequestRepository.findOne({
where: { where: {
id: withdrawRequestId, id: withdrawRequestId,
user: { id: userId } user: { id: userId },
} },
}) });
if (!request) { if (!request) {
throw new NotFoundException() throw new NotFoundException();
}
if (request.paidAt) {
throw new BadRequestException("درخواست پرداخت شده قابل حذف نیست")
} }
return this.withdrawRequestRepository.remove(request) if (request.paidAt) {
throw new BadRequestException('درخواست پرداخت شده قابل حذف نیست');
}
return this.withdrawRequestRepository.remove(request);
} }
// ---------------------------------------------------------------------------
// Confirm a withdraw request and create its wallet transaction atomically.
// ---------------------------------------------------------------------------
async confirmWithdrawRequest(requestId: string, transactionId: string) { async confirmWithdrawRequest(requestId: string, transactionId: string) {
const request = await this.withdrawRequestRepository.findOne({ where: { id: requestId }, relations: ['user'] }) const request = await this.withdrawRequestRepository.findOne({
where: { id: requestId },
relations: ['user'],
});
if (!request) { if (!request) {
throw new BadRequestException("درخواست برداشت یافت نشد .") throw new BadRequestException('درخواست برداشت یافت نشد .');
} }
if (request.paidAt) { if (request.paidAt) {
throw new BadRequestException("قبلا تایید شده است") throw new BadRequestException('قبلا تایید شده است');
} }
this.logger.log(`confirm withdraw request for request id ${requestId} `); this.logger.log(`confirm withdraw request for request id ${requestId} `);
@@ -272,14 +327,14 @@ export class ResellerService {
await queryRunner.connect(); await queryRunner.connect();
await queryRunner.startTransaction(); await queryRunner.startTransaction();
const amount = new Decimal(request.requestedAmount) const amount = new Decimal(request.requestedAmount);
await this.walletsService.createWithdrawTransaction(request.user.id, amount, request, queryRunner) await this.walletsService.createWithdrawTransaction(request.user.id, amount, request, queryRunner);
request.paidAt = new Date() request.paidAt = new Date();
request.transactionId = transactionId request.transactionId = transactionId;
await queryRunner.manager.save(request) await queryRunner.manager.save(request);
await queryRunner.commitTransaction(); await queryRunner.commitTransaction();
return true; return true;
@@ -292,69 +347,86 @@ export class ResellerService {
} }
} }
//********* Bank Account ***********/ // ---------------------------------------------------------------------------
// Return all bank accounts registered for a user.
// ---------------------------------------------------------------------------
async getUserBankAccount(userId: string) { async getUserBankAccount(userId: string) {
return this.userBankAccountRepository.find({ return this.userBankAccountRepository.find({
where: { user: { id: userId } } where: { user: { id: userId } },
}) });
} }
//********* Bank Account ***********/ // ---------------------------------------------------------------------------
// Create a bank account if the iban and user record are not duplicated.
// ---------------------------------------------------------------------------
async createUserBankAccount(userId: string, dto: CreateUserBankAccountDto) { async createUserBankAccount(userId: string, dto: CreateUserBankAccountDto) {
const { user } = await this.usersService.findOneById(userId) const { user } = await this.usersService.findOneById(userId);
const exitingBankAccount = await this.userBankAccountRepository.findOne({ const exitingBankAccount = await this.userBankAccountRepository.findOne({
where: { where: [
IBan: dto.IBan {
} IBan: dto.IBan,
}) },
{
user: { id: userId },
},
],
});
if (exitingBankAccount) { if (exitingBankAccount) {
throw new BadRequestException("شماره شبا موجود می باشد .") throw new BadRequestException('شماره شبا موجود می باشد .');
} }
const account = this.userBankAccountRepository.create({ const account = this.userBankAccountRepository.create({
user, user,
...dto ...dto,
}) });
return this.userBankAccountRepository.save(account) return this.userBankAccountRepository.save(account);
} }
//********* Remove Bank Account ***********/ // ---------------------------------------------------------------------------
// Delete a bank account that belongs to the user.
// ---------------------------------------------------------------------------
async deleteUserBankAccount(userId: string, bankAccountId: string) { async deleteUserBankAccount(userId: string, bankAccountId: string) {
return this.userBankAccountRepository.delete({ return this.userBankAccountRepository.delete({
user: { id: userId }, user: { id: userId },
id: bankAccountId id: bankAccountId,
}) });
} }
//*********** Referral Code ************/ // ---------------------------------------------------------------------------
// Resolve referral code lookup based on reseller or agent role.
// ---------------------------------------------------------------------------
async getReferralCodes(userId: string, type: ResellerType, query: PaginationDto) { async getReferralCodes(userId: string, type: ResellerType, query: PaginationDto) {
if (type == ResellerType.agent) { if (type === ResellerType.agent) {
return this.findAgentReferralCodes(userId, query) return this.findAgentReferralCodes(userId, query);
} else if (type == ResellerType.reseller) { }
return this.findResellerAgentsReferralCodes(userId, query)
if (type === ResellerType.reseller) {
return this.findResellerAgentsReferralCodes(userId, query);
} }
} }
// ---------------------------------------------------------------------------
// Return referral codes created by all agents under a reseller.
// ---------------------------------------------------------------------------
private async findResellerAgentsReferralCodes(userId: string, query: SearchWithdrawRequestQueryDto) { private async findResellerAgentsReferralCodes(userId: string, query: SearchWithdrawRequestQueryDto) {
const reselller = await this.FindOneOrFailByUserId(userId) const reseller = await this.FindOneOrFailByUserId(userId);
const agentIds = reselller.agents.map(ag => ag.id) const agentIds = reseller.agents.map((agent) => agent.id);
if (!agentIds.length) { if (!agentIds.length) {
return { codes: [], count: 0, paginate: true }; return { codes: [], count: 0, paginate: true };
} }
return this.referralService.getReferralCodesByUserIds(agentIds, query) return this.referralService.getReferralCodesByUserIds(agentIds, query);
} }
// ---------------------------------------------------------------------------
// Return referral codes created by a single agent.
// ---------------------------------------------------------------------------
private async findAgentReferralCodes(userId: string, query: SearchWithdrawRequestQueryDto) { private async findAgentReferralCodes(userId: string, query: SearchWithdrawRequestQueryDto) {
return this.referralService.getReferralCodesByUserIds([userId], query) return this.referralService.getReferralCodesByUserIds([userId], query);
} }
} }