diff --git a/database/migrations/1767508304292-AddPlanToInvoiceItem.ts b/database/migrations/1767508304292-AddPlanToInvoiceItem.ts new file mode 100644 index 0000000..213dba0 --- /dev/null +++ b/database/migrations/1767508304292-AddPlanToInvoiceItem.ts @@ -0,0 +1,39 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddPlanToInvoiceItem1767508304292 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "invoice_item" + ADD COLUMN "planId" uuid + `); + + await queryRunner.query(` + ALTER TABLE "invoice_item" + ADD CONSTRAINT "FK_invoice_item_plan" + FOREIGN KEY ("planId") REFERENCES "subscription_plan"("id") + ON DELETE RESTRICT ON UPDATE NO ACTION + `); + + await queryRunner.query(` + CREATE INDEX "IDX_invoice_item_plan" ON "invoice_item" ("planId") + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "invoice_item" + DROP CONSTRAINT "FK_invoice_item_plan" + `); + + await queryRunner.query(` + DROP INDEX "IDX_invoice_item_plan" + `); + + await queryRunner.query(` + ALTER TABLE "invoice_item" + DROP COLUMN "planId" + `); + } + +} diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 91f8ea6..f50aba4 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -524,6 +524,9 @@ export const enum SubscriptionMessage { FREE_PLAN_LIMIT_EXCEEDED = "شما در یک ماه گذشته از پلن رایگان استفاده کرده‌اید و نمی‌توانید مجدداً استفاده کنید", FREE_PLAN_NOT_ALLOWED = "انتخاب پلن رایگان در تمدید سرویس امکان پدیر نیست", USER_DOES_NOT_HAVE_ACCESS = "شما دسترسی به این سرویس ندارید", + SERVICE_MISMATCH = "سرویس پلن فعلی با سرویس پلن جدید متفاوت است", + UPGRADE_TO_LOWER_OR_EQUAL_PLAN_NOT_ALLOWED = "ارتقا به پلن با قیمت کمتر یا برابر مجاز نیست", + SUBSCRIPTION_EXPIRED_CANNOT_UPGRADE = "اشتراک منقضی شده و قابل ارتقا نیست", } export const enum InvoiceMessage { diff --git a/src/modules/dmenu/dmenu.module.ts b/src/modules/dmenu/dmenu.module.ts index 1ae3179..88a46a5 100644 --- a/src/modules/dmenu/dmenu.module.ts +++ b/src/modules/dmenu/dmenu.module.ts @@ -1,4 +1,4 @@ -import { Module } from "@nestjs/common"; +import { forwardRef, Module } from "@nestjs/common"; import { DMENU_CONFIG } from "./constants"; import { DmenuController } from "./dmenu.controller"; @@ -9,7 +9,7 @@ import { dmenuConfig } from "../../configs/icons.config"; import { SubscriptionsModule } from "../subscriptions/subscriptions.module"; @Module({ - imports: [SubscriptionsModule], + imports: [forwardRef(() => SubscriptionsModule)], controllers: [DmenuController], providers: [ IconsService, @@ -21,5 +21,6 @@ import { SubscriptionsModule } from "../subscriptions/subscriptions.module"; inject: dmenuConfig().inject, }, ], - }) + exports: [RestaurantService], +}) export class DmenuModule {} diff --git a/src/modules/dmenu/interface/plan.interface.ts b/src/modules/dmenu/interface/plan.interface.ts index fa873c0..98be71c 100644 --- a/src/modules/dmenu/interface/plan.interface.ts +++ b/src/modules/dmenu/interface/plan.interface.ts @@ -13,4 +13,16 @@ export interface ISetupRestaurant { subscriptionId: string; subscriptionEndDate: string|Date; subscriptionStartDate: string|Date; +} + +export interface IExternalApiPaginationMeta { + total: number; + page: number; + limit: number; + totalPages: number; +} + +export interface IExternalApiResponse { + data: T; + meta: IExternalApiPaginationMeta; } \ No newline at end of file diff --git a/src/modules/dmenu/providers/restaurant.service.ts b/src/modules/dmenu/providers/restaurant.service.ts index dd0783e..25f1e43 100644 --- a/src/modules/dmenu/providers/restaurant.service.ts +++ b/src/modules/dmenu/providers/restaurant.service.ts @@ -10,7 +10,7 @@ import { FindRestaurantsDto } from "../DTO/find-restaurants.dto"; import { SetupRestaurantDto } from "../DTO/create-restaurant.dto"; import { UpdateRestaurantDto } from "../DTO/update-restaurant.dto"; import { CreateMyRestaurantAdminDto } from "../DTO/create-restaurant-admin.dto"; -import { PlanEnum, ISetupRestaurant } from "../interface/plan.interface"; +import { PlanEnum, ISetupRestaurant, IExternalApiResponse } from "../interface/plan.interface"; import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service"; import { SubscriptionMessage } from "../../../common/enums/message.enum"; @@ -55,7 +55,7 @@ export class RestaurantService { const { data } = await firstValueFrom( this.httpService - .get(`${this.config.baseUrl}/super-admin/restaurants`, { + .get>(`${this.config.baseUrl}/super-admin/restaurants`, { params, headers: this.getHeaders(), }) @@ -66,7 +66,11 @@ export class RestaurantService { }), ), ); - return data; + return { + restaurants: data.data, + count: data.meta.total, + paginate: true, + }; } catch (error: unknown) { if (error instanceof AxiosError && error.response) { this.logger.error(`External API error response:`, error.response.data); @@ -331,4 +335,36 @@ export class RestaurantService { throw error; } } + + async upgradeSubscription(subscriptionId: string, newPlan: string, subscriptionEndDate: string) { + try { + const { data } = await firstValueFrom( + this.httpService + .patch(`${this.config.baseUrl}/super-admin/restaurants/subscription/${subscriptionId}/upgrade`, { + plan: newPlan, + subscriptionEndDate, + }, { + headers: this.getHeaders(), + }) + .pipe( + catchError((err: AxiosError) => { + this.logger.error(`Failed to upgrade restaurant subscription: ${err.message}`, err.stack); + return throwError(() => err); + }), + ), + ); + return data; + } catch (error: unknown) { + if (error instanceof AxiosError && error.response) { + this.logger.error(`External API error response:`, error.response.data); + const errorResponse = { + ...error.response.data, + message: error.response.data.error?.message || error.message, + }; + throw new HttpException(errorResponse, error.response.status); + } + this.logger.error(`Error upgrading restaurant subscription: ${error instanceof Error ? error.message : "Unknown error"}`); + throw error; + } + } } diff --git a/src/modules/invoices/entities/invoice-item.entity.ts b/src/modules/invoices/entities/invoice-item.entity.ts index 4e292a0..f1a0ecc 100755 --- a/src/modules/invoices/entities/invoice-item.entity.ts +++ b/src/modules/invoices/entities/invoice-item.entity.ts @@ -1,10 +1,11 @@ import Decimal from "decimal.js"; -import { Column, Entity, ManyToOne } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; import { Invoice } from "./invoice.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { DecimalTransformer } from "../../../common/transformers/decimal.transformer"; import { UserSubscription } from "../../subscriptions/entities/user-subscription.entity"; +import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; import { UserSupportPlan } from "../../support-plans/entities/user-support-plan.entity"; @Entity() export class InvoiceItem extends BaseEntity { @@ -29,6 +30,13 @@ export class InvoiceItem extends BaseEntity { @ManyToOne(() => UserSubscription, { nullable: true, onDelete: "RESTRICT" }) subscriptionPlan: UserSubscription | null; + @ManyToOne(() => SubscriptionPlan, { nullable: true, onDelete: "RESTRICT" }) + @JoinColumn({ name: "planId" }) + plan: SubscriptionPlan | null; + @ManyToOne(() => UserSupportPlan, { nullable: true, onDelete: "RESTRICT" }) supportPlan: UserSupportPlan | null; + + @Column({ type: "uuid", nullable: true }) + planId: string | null; } diff --git a/src/modules/invoices/invoices.module.ts b/src/modules/invoices/invoices.module.ts index 2a6c588..69f2122 100755 --- a/src/modules/invoices/invoices.module.ts +++ b/src/modules/invoices/invoices.module.ts @@ -1,5 +1,5 @@ import { BullModule } from "@nestjs/bullmq"; -import { Module } from "@nestjs/common"; +import { forwardRef, Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; import { INVOICE } from "./constants"; @@ -11,6 +11,7 @@ import { InvoiceProcessor } from "./queue/invoice.processor"; import { InvoiceItemsRepository } from "./repositories/invoice-items.repository"; import { InvoicesRepository } from "./repositories/invoices.repository"; import { AccessLogsModule } from "../access-logs/access-logs.module"; +import { DmenuModule } from "../dmenu/dmenu.module"; import { Discount } from "../discounts/entities/discount.entity"; import { UsageDiscount } from "../discounts/entities/usage-discount.entity"; import { DiscountRepository } from "../discounts/repositories/discounts.repository"; @@ -46,6 +47,7 @@ import { WalletsModule } from "../wallets/wallets.module"; NotificationModule, PaymentsModule, AccessLogsModule, + forwardRef(() => DmenuModule), ], providers: [InvoicesService, InvoicesRepository, InvoiceItemsRepository, DiscountRepository, UsageDiscountRepository, InvoiceProcessor], controllers: [InvoicesController], diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index 5eb1abd..4058ad5 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -11,6 +11,7 @@ import { OperationType } from "../../access-logs/enums/operation-type.enum"; import { UserType } from "../../access-logs/enums/user-type.enum"; import { AccessLogService } from "../../access-logs/providers/access-log.service"; import { VerifyOtpWithUserId } from "../../auth/DTO/verify-otp.dto"; +import { RestaurantService } from "../../dmenu/providers/restaurant.service"; import { Discount } from "../../discounts/entities/discount.entity"; import { DiscountType } from "../../discounts/enums/discount-type.enum"; import { NotificationQueue } from "../../notifications/queue/notification.queue"; @@ -53,6 +54,7 @@ export class InvoicesService { private readonly smsService: SmsService, private readonly dataSource: DataSource, private readonly accessLogService: AccessLogService, + private readonly restaurantService: RestaurantService, ) {} ///********************************** */ @@ -498,6 +500,7 @@ export class InvoicesService { unitPrice: originalPrice, discount: discount ? new Decimal(originalPrice).sub(finalPrice).toNumber() : 0, subscriptionPlan: userSub, + plan: plan, totalPrice: finalPrice, }; @@ -540,6 +543,63 @@ export class InvoicesService { return invoice; } + + //********************************** */ + async createInvoiceForSubscriptionUpgrade(user: User, plan: SubscriptionPlan, userSub: UserSubscription, dueDate: Date, qryRnr: QueryRunner, upgradeCost?: Decimal) { + const discount = plan.directDiscount; + const originalPrice = upgradeCost || (plan.originalPrice || plan.price); + const finalPrice = upgradeCost || plan.price; + + const invoiceItem = { + name: `${plan.service.name} - Upgrade`, + count: 1, + unitPrice: originalPrice, + discount: discount ? new Decimal(originalPrice).sub(finalPrice).toNumber() : 0, + subscriptionPlan: userSub, + plan: plan, + totalPrice: finalPrice, + }; + + const taxAmount = new Decimal(finalPrice).mul(0.1); + const totalPrice = new Decimal(finalPrice).add(taxAmount); + + const invoice = qryRnr.manager.create(Invoice, { + user, + totalPrice: totalPrice, + originalPrice: new Decimal(originalPrice).add(new Decimal(originalPrice).mul(0.1)), + tax: taxAmount.toNumber(), + status: InvoiceStatus.WAIT_PAYMENT, + dueDate, + items: [invoiceItem], + discount: discount || undefined, + }); + + await qryRnr.manager.save(Invoice, invoice); + + await this.notificationQueue.addInvoiceCreationNotification(user.id, { + invoiceId: invoice.numericId.toString(), + dueDate: invoice.dueDate, + createDate: invoice.createdAt, + price: totalPrice.toNumber(), + userPhone: user.phone, + userEmail: user.email, + items: plan.service.name, + }); + + await this.invoiceQueue.add( + INVOICE.REMINDER_JOB_NAME, + { invoiceId: invoice.id }, + { + delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()), + attempts: INVOICE.REMINDER_JOB_ATTEMPTS, + backoff: { type: "exponential", delay: INVOICE.REMINDER_JOB_BACKOFF }, + priority: INVOICE.REMINDER_JOB_PRIORITY, + }, + ); + + return invoice; + } + //********************************** */ async createInvoiceForSupportPlan( user: User, @@ -735,18 +795,51 @@ export class InvoicesService { if (invoice.items[0]?.subscriptionPlan && !invoice.isExternal) { const userSubscription = invoice.items[0].subscriptionPlan; + const invoiceItem = invoice.items[0]; - const isRenewal = userSubscription.status === SubscriptionStatus.ACTIVE; + // Check if this is an upgrade (subscription is ACTIVE and plan in invoice differs from current plan) + const isUpgrade = userSubscription.status === SubscriptionStatus.ACTIVE && + invoiceItem.plan && + invoiceItem.plan.id !== userSubscription.plan.id; - if (isRenewal) { - const newEndDate = dayjs(userSubscription.endDate).add(userSubscription.plan.duration, "day").toDate(); - userSubscription.endDate = newEndDate; + if (isUpgrade) { + // This is an upgrade - update subscription with the new plan + const upgradePlan = invoiceItem.plan!; - this.logger.log(`Subscription ${userSubscription.id} renewed until ${newEndDate}`); + userSubscription.plan = upgradePlan; + this.logger.log(`Subscription ${userSubscription.id} upgraded to plan ${upgradePlan.name}`); await this.scheduleNextRenewalJob(userSubscription); + + // Call external API for dmenu service upgrades + if (upgradePlan.service.name.toLowerCase().includes('dmenu') || upgradePlan.service.slug?.toLowerCase().includes('dmenu')) { + try { + this.logger.log(`Calling external API for dmenu upgrade: subscription ${userSubscription.id}`); + const dmenuPlan = upgradePlan.name.includes("دلیوری") ? "premium" : "base"; + await this.restaurantService.upgradeSubscription( + userSubscription.id, + dmenuPlan, + userSubscription.endDate.toISOString() + ); + this.logger.log(`External API call completed for dmenu upgrade: subscription ${userSubscription.id}`); + } catch (error) { + this.logger.error(`Failed to call external API for dmenu upgrade: ${error instanceof Error ? error.message : 'Unknown error'}`); + // Don't fail the transaction if external API call fails + } + } } else { - userSubscription.status = SubscriptionStatus.ACTIVE; - await this.scheduleNextRenewalJob(userSubscription); + // Regular subscription logic (renewal or activation) + const isRenewal = userSubscription.status === SubscriptionStatus.ACTIVE; + + if (isRenewal) { + const newEndDate = dayjs(userSubscription.endDate).add(userSubscription.plan.duration, "day").toDate(); + userSubscription.endDate = newEndDate; + + this.logger.log(`Subscription ${userSubscription.id} renewed until ${newEndDate}`); + await this.scheduleNextRenewalJob(userSubscription); + } else { + userSubscription.status = SubscriptionStatus.ACTIVE; + await this.scheduleNextRenewalJob(userSubscription); + } } // @@ -849,6 +942,7 @@ export class InvoicesService { subscriptionPlan: { plan: true, }, + plan: true, supportPlan: { supportPlan: true, }, diff --git a/src/modules/subscriptions/DTO/upgrade-subscription.dto.ts b/src/modules/subscriptions/DTO/upgrade-subscription.dto.ts new file mode 100644 index 0000000..1e18030 --- /dev/null +++ b/src/modules/subscriptions/DTO/upgrade-subscription.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty, IsString } from "class-validator"; + +export class UpgradeSubscriptionDto { + @ApiProperty({ + description: "The ID of the new plan to upgrade to", + example: "plan-uuid-123" + }) + @IsString() + @IsNotEmpty() + planId: string; +} diff --git a/src/modules/subscriptions/providers/subscriptions.service.ts b/src/modules/subscriptions/providers/subscriptions.service.ts index 55c2d44..a22693e 100755 --- a/src/modules/subscriptions/providers/subscriptions.service.ts +++ b/src/modules/subscriptions/providers/subscriptions.service.ts @@ -1,5 +1,6 @@ import { InjectQueue } from "@nestjs/bullmq"; import { BadRequestException, ForbiddenException, Injectable, Logger } from "@nestjs/common"; +import { ModuleRef } from "@nestjs/core"; import { Queue } from "bullmq"; import dayjs from "dayjs"; import Decimal from "decimal.js"; @@ -35,11 +36,15 @@ export class SubscriptionsService { @InjectQueue(SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME) private readonly provisioningQueue: Queue, private readonly subscriptionsPlanRepository: SubscriptionsPlanRepository, private readonly userSubscriptionsRepository: UserSubscriptionsRepository, - private readonly invoicesService: InvoicesService, + private readonly moduleRef: ModuleRef, private readonly usersService: UsersService, private readonly danakServices: DanakServicesService, private readonly dataSource: DataSource, - ) {} + ) { } + + private get invoicesService(): InvoicesService { + return this.moduleRef.get(InvoicesService, { strict: false }); + } //************************************ */ async createSubscriptionsPlan(createDto: AddSubscriptionsToServiceDto) { @@ -361,6 +366,98 @@ export class SubscriptionsService { } } + //************************************ */ + async upgradeSubscription(userSubscriptionId: string, planId: string, userId: string) { + const queryRunner = this.dataSource.createQueryRunner(); + + try { + await queryRunner.connect(); + await queryRunner.startTransaction(); + + const userSubscription = await queryRunner.manager.findOne(UserSubscription, { + where: { id: userSubscriptionId, user: { id: userId } }, + relations: { plan: true } + }); + if (!userSubscription) throw new BadRequestException(SubscriptionMessage.USER_SUBS_NOT_FOUND); + + const newPlan = await this.subscriptionsPlanRepository.findOneBy({ id: planId }); + if (!newPlan) throw new BadRequestException(SubscriptionMessage.PLAN_NOT_FOUND); + + if (userSubscription.plan.service.id !== newPlan.service.id) { + throw new BadRequestException(SubscriptionMessage.SERVICE_MISMATCH); + } + + // Validate that new plan is more expensive than current plan + if (new Decimal(newPlan.price).lessThanOrEqualTo(userSubscription.plan.price)) { + throw new BadRequestException(SubscriptionMessage.UPGRADE_TO_LOWER_OR_EQUAL_PLAN_NOT_ALLOWED); + } + + // Calculate remaining days in current subscription + const today = dayjs(); + const endDate = dayjs(userSubscription.endDate); + const remainingDays = Math.max(0, endDate.diff(today, 'day')); + + if (remainingDays <= 0) { + throw new BadRequestException(SubscriptionMessage.SUBSCRIPTION_EXPIRED_CANNOT_UPGRADE); + } + + // Calculate daily rates + const currentPlanDuration = userSubscription.plan.duration; + const newPlanDuration = newPlan.duration; + + const currentDailyRate = new Decimal(userSubscription.plan.price).div(currentPlanDuration); + const newDailyRate = new Decimal(newPlan.price).div(newPlanDuration); + + // Calculate price difference per day + const dailyPriceDifference = newDailyRate.sub(currentDailyRate); + + // Calculate prorated upgrade cost for remaining days + const proratedUpgradeCost = dailyPriceDifference.mul(remainingDays); + + // Ensure minimum charge amount + const minimumCharge = new Decimal(1000); // 1000 IRR minimum + const finalUpgradeCost = Decimal.max(proratedUpgradeCost, minimumCharge); + + const user = await this.usersService.findOneByIdWithQueryRunner(userSubscription.user.id, queryRunner); + + const invoiceDueDate = dayjs().add(INVOICE.DUEDATE, "day").toDate(); + + const invoice = await this.invoicesService.createInvoiceForSubscriptionUpgrade( + user, + newPlan, + userSubscription, + invoiceDueDate, + queryRunner, + finalUpgradeCost + ); + + // Update the subscription to the new plan + userSubscription.plan = newPlan; + await queryRunner.manager.save(UserSubscription, userSubscription); + + // Add provisioning job for the new plan + await this.addProvisioningJob(userSubscription, newPlan, user); + + await queryRunner.commitTransaction(); + return { + message: SubscriptionMessage.INVOICE_CREATED_SUCCESSFULLY, + userSubscription, + invoice, + upgradeDetails: { + remainingDays, + currentPlanPrice: userSubscription.plan.price, + newPlanPrice: newPlan.price, + proratedUpgradeCost: finalUpgradeCost.toNumber(), + }, + }; + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } + //************************************ */ async getServiceSubscriptions(serviceId: string, queryDto: ServiceSubsQueryDto) { const service = await this.danakServices.findServiceById(serviceId); diff --git a/src/modules/subscriptions/subscriptions.controller.ts b/src/modules/subscriptions/subscriptions.controller.ts index d0fd054..6342489 100755 --- a/src/modules/subscriptions/subscriptions.controller.ts +++ b/src/modules/subscriptions/subscriptions.controller.ts @@ -8,6 +8,7 @@ import { ServiceSubsQueryDto } from "./DTO/service-subs-query.dto"; import { ExtendUSerSubscribeServiceDto, SubscribeServiceDto } from "./DTO/subscribe-service.dto"; import { SubscriptionAccessDto } from "./DTO/subscription-access-dto"; import { UpdateSubscriptionPlanDto } from "./DTO/update-subscription.dto"; +import { UpgradeSubscriptionDto } from "./DTO/upgrade-subscription.dto"; import { UserSubscriptionIdParamDto } from "./DTO/user-subscription-id.param.dto"; import { SubscriptionsService } from "./providers/subscriptions.service"; import { UserQuickAccessService } from "./providers/users-quick-access.service"; @@ -90,6 +91,16 @@ export class SubscriptionsController { return this.subscriptionService.extendSubscribeToPlan(paramDto.userSubscriptionId, subscribeDto, userId); } + @ApiOperation({ summary: "Upgrade subscription plan ==> user route" }) + @Put(":userSubscriptionId/upgrade") + upgradeSubscription( + @Param() paramDto: UserSubscriptionIdParamDto, + @Body() upgradeDto: UpgradeSubscriptionDto, + @UserDec("id") userId: string, + ) { + return this.subscriptionService.upgradeSubscription(paramDto.userSubscriptionId, upgradeDto.planId, userId); + } + @ApiOperation({ summary: "add quick access a service ==> user route" }) @Post(":id/add-quick-access") addQuickAccess(@Param() paramDto: ParamDto, @UserDec("id") userId: string) { diff --git a/src/modules/subscriptions/subscriptions.module.ts b/src/modules/subscriptions/subscriptions.module.ts index b5dfdd2..2894805 100755 --- a/src/modules/subscriptions/subscriptions.module.ts +++ b/src/modules/subscriptions/subscriptions.module.ts @@ -1,5 +1,5 @@ import { BullModule } from "@nestjs/bullmq"; -import { Module } from "@nestjs/common"; +import { forwardRef, Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; import { SUBSCRIPTIONS } from "./constants"; @@ -24,10 +24,10 @@ import { UserQuickAccessRepository } from "./repositories/users-quick-access.rep name: SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME, prefix: SUBSCRIPTIONS.PROVISIONING_QUEUE_PREFIX, }), + forwardRef(() => InvoicesModule), DanakServicesModule, UsersModule, WalletsModule, - InvoicesModule, ], providers: [ SubscriptionsService, diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index 9d021f5..764cc56 100755 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -1,6 +1,6 @@ import { INestApplication } from "@nestjs/common"; import { Test, TestingModule } from "@nestjs/testing"; -import * as request from "supertest"; +import request from "supertest"; import { AppModule } from "./../src/app.module";