diff --git a/src/modules/invoices/entities/invoice-item.entity.ts b/src/modules/invoices/entities/invoice-item.entity.ts index 87b381a..c4a7c95 100755 --- a/src/modules/invoices/entities/invoice-item.entity.ts +++ b/src/modules/invoices/entities/invoice-item.entity.ts @@ -5,7 +5,7 @@ import { Column, Entity, ManyToOne } from "typeorm"; import { Invoice } from "./invoice.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { DecimalTransformer } from "../../../common/transformers/decimal.transformer"; -import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; +import { UserSubscription } from "../../subscriptions/entities/user-subscription.entity"; @Entity() export class InvoiceItem extends BaseEntity { @@ -27,6 +27,6 @@ export class InvoiceItem extends BaseEntity { @Column({ type: "decimal", precision: 16, scale: 2, nullable: false, transformer: new DecimalTransformer() }) totalPrice: Decimal; - @ManyToOne(() => SubscriptionPlan, { nullable: true, onDelete: "RESTRICT" }) - subscriptionPlan: SubscriptionPlan | null; + @ManyToOne(() => UserSubscription, { nullable: true, onDelete: "RESTRICT" }) + subscriptionPlan: UserSubscription | null; } diff --git a/src/modules/invoices/enums/invoice-status.enum.ts b/src/modules/invoices/enums/invoice-status.enum.ts index 7bfc5ba..43e54c1 100755 --- a/src/modules/invoices/enums/invoice-status.enum.ts +++ b/src/modules/invoices/enums/invoice-status.enum.ts @@ -1,7 +1,5 @@ export enum InvoiceStatus { PENDING = "PENDING", - APPROVED = "APPROVED", - REJECTED = "REJECTED", WAIT_PAYMENT = "WAIT_PAYMENT", PAID = "PAID", EXPIRED = "EXPIRED", diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index de158a4..0ffee2e 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -11,6 +11,8 @@ import { DiscountRepository } from "../../discounts/repositories/discount.reposi import { UsageDiscountRepository } from "../../discounts/repositories/usage-discount.repository"; import { NotificationsService } from "../../notifications/providers/notifications.service"; import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; +import { UserSubscription } from "../../subscriptions/entities/user-subscription.entity"; +import { SubscriptionStatus } from "../../subscriptions/enums/subscription-status.enum"; import { UsersService } from "../../users/providers/users.service"; import { OTPService } from "../../utils/providers/otp.service"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; @@ -110,9 +112,13 @@ export class InvoicesService { const invoice = await this.invoiceRepository.findOne({ where: { id: invoiceId, user: { id: userId } }, relations: { items: true } }); if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); - if (invoice.status === InvoiceStatus.APPROVED) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED); + if (invoice.status === InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED); if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID); - if (dayjs().isAfter(invoice.dueDate)) throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE); + if (dayjs().isAfter(invoice.dueDate)) { + invoice.status = InvoiceStatus.EXPIRED; + await this.invoiceRepository.save(invoice); + throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE); + } if (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_APPROVED); @@ -149,14 +155,13 @@ export class InvoicesService { const isValid = await this.otpService.verifyOtp(user.phone, code, "INVOICE_VERIFY"); if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); - if (invoice.status === InvoiceStatus.REJECTED) throw new BadRequestException(InvoiceMessage.INVOICE_IS_REJECTED); - if (invoice.status === InvoiceStatus.APPROVED) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED); + if (invoice.status === InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.ALREADY_APPROVED); if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID); if (dayjs().isAfter(invoice.dueDate)) throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE); if (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_APPROVED); - invoice.status = InvoiceStatus.APPROVED; + invoice.status = InvoiceStatus.WAIT_PAYMENT; await this.invoiceRepository.save(invoice); @@ -167,14 +172,27 @@ export class InvoicesService { } ///********************************** */ - async createInvoiceForSubscription(userId: string, plan: SubscriptionPlan, dueDate: Date, queryRunner: QueryRunner) { + async createInvoiceForSubscription( + userId: string, + plan: SubscriptionPlan, + userSub: UserSubscription, + dueDate: Date, + queryRunner: QueryRunner, + ) { const invoiceItems = [ - { name: plan.service.name, count: 1, unitPrice: plan.price, discount: 0, subscriptionPlan: plan, totalPrice: plan.price }, + { + name: plan.service.name, + count: 1, + unitPrice: plan.price, + discount: 0, + subscriptionPlan: userSub, + totalPrice: plan.price, + }, ]; const invoice = queryRunner.manager.create(Invoice, { totalPrice: plan.price, user: { id: userId }, - status: InvoiceStatus.PAID, + status: InvoiceStatus.PENDING, paidAt: new Date(), dueDate, items: invoiceItems, @@ -238,7 +256,9 @@ export class InvoicesService { .andWhere("invoice.user.id = :userId", { userId: userId }) .leftJoinAndSelect("invoice.items", "items") .leftJoinAndSelect("items.subscriptionPlan", "subscriptionPlan", "subscriptionPlan.id IS NOT NULL") - .leftJoinAndSelect("subscriptionPlan.service", "service", "subscriptionPlan.id IS NOT NULL"); + .leftJoinAndSelect("subscriptionPlan.plan", "plan", "subscriptionPlan.id IS NOT NULL") + .leftJoin("plan.service", "service", "subscriptionPlan.id IS NOT NULL") + .addSelect(["service.name", "service.id"]); if (queryDto.status) { queryBuilder.andWhere("invoice.status = :status", { status: queryDto.status }); @@ -265,27 +285,33 @@ export class InvoicesService { const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner); const invoice = await this.getPendingInvoiceByIdWithQueryRunner(invoiceId, user.id, queryRunner); + console.log(invoice); - if (invoice.status === InvoiceStatus.REJECTED) throw new BadRequestException(InvoiceMessage.INVOICE_IS_REJECTED); - if (invoice.status === InvoiceStatus.EXPIRED) throw new BadRequestException(InvoiceMessage.INVOICE_IS_OVERDUE); - if (invoice.status === InvoiceStatus.PAID) throw new BadRequestException(InvoiceMessage.INVOICE_ALREADY_PAID); - if (invoice.status !== InvoiceStatus.APPROVED) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_PAID); + if (invoice.status !== InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_PAID); const userWallet = await this.walletsService.getWalletByUserId(user.id, queryRunner); - if (new Decimal(userWallet.balance).lessThan(invoice.totalPrice)) throw new BadRequestException(WalletMessage.INSUFFICIENT_BALANCE); + if (userWallet.balance < invoice.totalPrice) throw new BadRequestException(WalletMessage.INSUFFICIENT_BALANCE); userWallet.balance = new Decimal(userWallet.balance).sub(invoice.totalPrice); await queryRunner.manager.save(Wallet, userWallet); - await this.walletsService.createInvoiceTransaction(invoice.totalPrice, userWallet.id, queryRunner); + if (invoice.items[0]?.subscriptionPlan) { + const userSubscription = invoice.items[0].subscriptionPlan; + userSubscription.status = SubscriptionStatus.ACTIVE; + // + await queryRunner.manager.save(UserSubscription, userSubscription); + await this.walletsService.createSubscriptionTransaction(invoice.totalPrice, userWallet.id, queryRunner); + // + } else { + await this.walletsService.createInvoiceTransaction(invoice.totalPrice, userWallet.id, queryRunner); + } invoice.status = InvoiceStatus.PAID; - invoice.paidAt = new Date(); + invoice.paidAt = dayjs().toDate(); await queryRunner.manager.save(Invoice, invoice); - console.log(invoice); await queryRunner.commitTransaction(); return { message: InvoiceMessage.INVOICE_PAID, @@ -302,7 +328,20 @@ export class InvoicesService { //*********************************** */ async getPendingInvoiceByIdWithQueryRunner(invoiceId: string, userId: string, queryRunner: QueryRunner) { - const invoice = await queryRunner.manager.findOneBy(Invoice, { user: { id: userId }, status: InvoiceStatus.PENDING, id: invoiceId }); + const invoice = await queryRunner.manager.findOne(Invoice, { + where: { + user: { id: userId }, + status: InvoiceStatus.WAIT_PAYMENT, + id: invoiceId, + }, + relations: { + items: { + subscriptionPlan: { + plan: true, + }, + }, + }, + }); if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); return invoice; } diff --git a/src/modules/notifications/providers/notifications.service.ts b/src/modules/notifications/providers/notifications.service.ts index bc966d0..e8945c2 100755 --- a/src/modules/notifications/providers/notifications.service.ts +++ b/src/modules/notifications/providers/notifications.service.ts @@ -107,7 +107,7 @@ export class NotificationsService { //************************ */ async markAllAsRead(userId: string) { - await this.notificationRepository.delete({ recipient: { id: userId } }); + await this.notificationRepository.update({ recipient: { id: userId } }, { isRead: true }); return { message: CommonMessage.UPDATE_SUCCESS, }; diff --git a/src/modules/subscriptions/entities/user-subscription.entity.ts b/src/modules/subscriptions/entities/user-subscription.entity.ts index bd206ca..d173814 100755 --- a/src/modules/subscriptions/entities/user-subscription.entity.ts +++ b/src/modules/subscriptions/entities/user-subscription.entity.ts @@ -28,12 +28,6 @@ export class UserSubscription extends BaseEntity { @Column({ type: "text", nullable: false }) description: string; - @Column({ type: "enum", enum: SubscriptionStatus, default: SubscriptionStatus.PENDING }) + @Column({ type: "enum", enum: SubscriptionStatus, default: SubscriptionStatus.INACTIVE }) status: SubscriptionStatus; - - @Column({ type: "boolean", default: false }) - isCanceled: boolean; - - // @OneToMany(() => UserQuickAccess, (quickAccess) => quickAccess.userSubscription) - // quickAccess: UserQuickAccess[]; } diff --git a/src/modules/subscriptions/enums/subscription-status.enum.ts b/src/modules/subscriptions/enums/subscription-status.enum.ts index 1e2bc5f..12f269e 100755 --- a/src/modules/subscriptions/enums/subscription-status.enum.ts +++ b/src/modules/subscriptions/enums/subscription-status.enum.ts @@ -1,7 +1,7 @@ export enum SubscriptionStatus { ACTIVE = "ACTIVE", INACTIVE = "INACTIVE", - PENDING = "PENDING", + // PENDING = "PENDING", CANCELED = "CANCELED", EXPIRED = "EXPIRED", } diff --git a/src/modules/subscriptions/providers/subscriptions.service.ts b/src/modules/subscriptions/providers/subscriptions.service.ts index 457b443..d56961b 100755 --- a/src/modules/subscriptions/providers/subscriptions.service.ts +++ b/src/modules/subscriptions/providers/subscriptions.service.ts @@ -4,14 +4,14 @@ import dayjs from "dayjs"; import Decimal from "decimal.js"; import { DataSource, In } from "typeorm"; -import { ServiceMessage, SubscriptionMessage, WalletMessage } from "../../../common/enums/message.enum"; +import { ServiceMessage, SubscriptionMessage } from "../../../common/enums/message.enum"; import { DanakServicesService } from "../../danak-services/providers/danak-services.service"; import { DiscountCalculationType } from "../../discounts/enums/discount-type.enum"; import { InvoicesService } from "../../invoices/providers/invoices.service"; import { UsersService } from "../../users/providers/users.service"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; -import { Wallet } from "../../wallets/entities/wallet.entity"; -import { WalletsService } from "../../wallets/providers/wallets.service"; +// import { Wallet } from "../../wallets/entities/wallet.entity"; +// import { WalletsService } from "../../wallets/providers/wallets.service"; import { AddSubscriptionsToServiceDto } from "../DTO/create-subscription.dto"; import { SearchUserSubsQueryDto } from "../DTO/search-user-subs-query.dto"; import { ServiceSubsQueryDto } from "../DTO/service-subs-query.dto"; @@ -30,7 +30,7 @@ export class SubscriptionsService { private readonly userSubscriptionsRepository: UserSubscriptionsRepository, private readonly invoicesService: InvoicesService, private readonly usersService: UsersService, - private readonly walletsService: WalletsService, + // private readonly walletsService: WalletsService, private readonly danakServices: DanakServicesService, private readonly dataSource: DataSource, ) {} @@ -227,47 +227,18 @@ export class SubscriptionsService { const startDate = dayjs().toDate(); const endDate = dayjs().add(plan.duration, "day").toDate(); - // const startDate = new Date(); - // const endDate = new Date(); - // endDate.setDate(startDate.getDate() + plan.duration); + // const userSubscription = queryRunner.manager.create(UserSubscription, { ...subscribeDto, user, plan, startDate, endDate }); await queryRunner.manager.save(UserSubscription, userSubscription); - - const userWallet = await this.walletsService.getWalletByUserId(user.id, queryRunner); - - let finalPrice = new Decimal(plan.price); - - const activeDiscounts = - plan.discounts?.filter((discount) => discount.isActive && new Date() >= discount.startDate && new Date() <= discount.endDate) || []; - - activeDiscounts.forEach((discount) => { - if (discount.calculationType === DiscountCalculationType.PERCENTAGE) { - finalPrice = finalPrice.sub(finalPrice.mul(discount.amount).div(100)); - } else if (discount.calculationType === DiscountCalculationType.FIXED) { - finalPrice = finalPrice.sub(discount.amount); - } - }); - - plan.price = finalPrice; - - if (new Decimal(userWallet.balance).lessThan(finalPrice)) throw new BadRequestException(WalletMessage.INSUFFICIENT_BALANCE); - - // if (userWallet.balance < plan.price) throw new BadRequestException(WalletMessage.INSUFFICIENT_BALANCE); - - // - userWallet.balance = new Decimal(userWallet.balance).sub(finalPrice); - - await queryRunner.manager.save(Wallet, userWallet); // - await this.walletsService.createSubscriptionTransaction(finalPrice, userWallet.id, queryRunner); //TODO: need queue handling for notification - const invoiceDueDate = dayjs(userSubscription.endDate).subtract(3, "day").toDate(); + const invoiceDueDate = dayjs(userSubscription.startDate).add(5, "day").toDate(); - const invoice = await this.invoicesService.createInvoiceForSubscription(userId, plan, invoiceDueDate, queryRunner); - userSubscription.status = SubscriptionStatus.ACTIVE; + const invoice = await this.invoicesService.createInvoiceForSubscription(userId, plan, userSubscription, invoiceDueDate, queryRunner); + userSubscription.status = SubscriptionStatus.INACTIVE; await queryRunner.manager.save(UserSubscription, userSubscription); diff --git a/src/modules/tickets/entities/ticket.entity.ts b/src/modules/tickets/entities/ticket.entity.ts index d1e5299..c9e05cc 100755 --- a/src/modules/tickets/entities/ticket.entity.ts +++ b/src/modules/tickets/entities/ticket.entity.ts @@ -25,9 +25,8 @@ export class Ticket extends BaseEntity { @Column({ type: "enum", enum: TicketPriority, nullable: false }) priority: TicketPriority; - //TODO:set this to not nullable @ManyToOne(() => DanakService, (danakService) => danakService.tickets, { onDelete: "RESTRICT", nullable: true }) - danakService: DanakService; + danakService: DanakService | null; @ManyToOne(() => User, (user) => user.tickets, { nullable: true }) assignedTo: User; diff --git a/src/modules/wallets/providers/wallets.service.ts b/src/modules/wallets/providers/wallets.service.ts index 561b200..a47dfa5 100755 --- a/src/modules/wallets/providers/wallets.service.ts +++ b/src/modules/wallets/providers/wallets.service.ts @@ -92,6 +92,21 @@ export class WalletsService { return transaction; } + + //*********************************** */ + + async createInvoiceTransaction(amount: Decimal, walletId: string, queryRunner: QueryRunner) { + const transaction = queryRunner.manager.create(WalletTransaction, { + amount, + wallet: { id: walletId }, + type: TransactionType.DEBIT, + description: WalletMessage.INVOICE_WALLET_TRANSFER, + }); + + await queryRunner.manager.save(WalletTransaction, transaction); + + return transaction; + } //*********************************** */ async getTransaction(userId: string, paginationDto: PaginationDto) { @@ -189,19 +204,4 @@ export class WalletsService { if (!wallet) throw new BadRequestException(WalletMessage.WALLET_NOT_FOUND); return wallet; } - - //*********************************** */ - - async createInvoiceTransaction(amount: Decimal, walletId: string, queryRunner: QueryRunner) { - const transaction = queryRunner.manager.create(WalletTransaction, { - amount, - wallet: { id: walletId }, - type: TransactionType.DEBIT, - description: WalletMessage.INVOICE_WALLET_TRANSFER, - }); - - await queryRunner.manager.save(WalletTransaction, transaction); - - return transaction; - } }