diff --git a/src/modules/dmenu/DTO/buy-premium-plan.dto.ts b/src/modules/dmenu/DTO/buy-premium-plan.dto.ts new file mode 100644 index 0000000..f64a3e7 --- /dev/null +++ b/src/modules/dmenu/DTO/buy-premium-plan.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty, IsUUID } from "class-validator"; + +import { SubscriptionMessage } from "../../../common/enums/message.enum"; + +export class BuyPremiumPlanDto { + @IsNotEmpty({ message: SubscriptionMessage.PLAN_ID_REQUIRED }) + @IsUUID(4, { message: SubscriptionMessage.PLAN_ID_SHOULD_BE_UUID }) + @ApiProperty({ description: "The restaurant subscription ID to upgrade", example: "f7b3b3b3-7b3b-4b3b-8b3b-3b3b3b3b3b3b" }) + userSubscriptionId: string; +} diff --git a/src/modules/dmenu/DTO/create-restaurant.dto.ts b/src/modules/dmenu/DTO/create-restaurant.dto.ts index e6432c7..f6657aa 100644 --- a/src/modules/dmenu/DTO/create-restaurant.dto.ts +++ b/src/modules/dmenu/DTO/create-restaurant.dto.ts @@ -22,6 +22,6 @@ export class CreateRestaurantDto { @ApiProperty({ example: 'sub_1234567890', description: 'شناسه اشتراک' }) @IsString() - subscriptionId!: string; + userSubscriptionId!: string; } diff --git a/src/modules/dmenu/dmenu.controller.ts b/src/modules/dmenu/dmenu.controller.ts index 09fcbb0..ec2927f 100644 --- a/src/modules/dmenu/dmenu.controller.ts +++ b/src/modules/dmenu/dmenu.controller.ts @@ -13,6 +13,7 @@ import { CreateRestaurantDto } from "./DTO/create-restaurant.dto"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { PermissionEnum } from "../users/enums/permission.enum"; +import { UserDec } from "../../common/decorators/user.decorator"; @ApiTags("d-menu") @Controller("admin/dmenu") @@ -112,7 +113,14 @@ export class DmenuController { @Post('restaurants') @ApiOperation({ summary: "Create restaurant" }) - createRestaurant(@Body() dto: CreateRestaurantDto) { - return this.restaurantService.createRestaurant(dto); + createRestaurant(@Body() dto: CreateRestaurantDto, @UserDec("id") userId: string) { + return this.restaurantService.createRestaurant(dto, userId); + } + + @Get('/super-admin/restaurants/subscription/:subscriptionId') + @ApiOperation({ summary: "Get restaurant subscription by subscription ID" }) + @ApiParam({ name: "subscriptionId", required: true, type: String }) + getRestaurantSubscription(@Param("subscriptionId") subscriptionId: string) { + return this.restaurantService.getRestaurantSubscription(subscriptionId); } } diff --git a/src/modules/dmenu/providers/premium.service.ts b/src/modules/dmenu/providers/premium.service.ts new file mode 100644 index 0000000..58498d8 --- /dev/null +++ b/src/modules/dmenu/providers/premium.service.ts @@ -0,0 +1,94 @@ +import { BadRequestException, Injectable } from "@nestjs/common"; +import dayjs from "dayjs"; +import Decimal from "decimal.js"; + +import { SubscriptionMessage } from "../../../common/enums/message.enum"; +import { InvoicesService } from "../../invoices/providers/invoices.service"; +import { UserSubscription } from "../../subscriptions/entities/user-subscription.entity"; +import { UserSubscriptionsRepository } from "../../subscriptions/repositories/user-subscriptions.repository"; +import { BuyPremiumPlanDto } from "../DTO/buy-premium-plan.dto"; + +@Injectable() +export class PremiumService { + constructor( + private readonly userSubscriptionsRepository: UserSubscriptionsRepository, + private readonly invoicesService: InvoicesService, + ) {} + + async buyPremiumPlan(dto: BuyPremiumPlanDto, userId: string) { + // Find the user's current subscription + const userSubscription = await this.userSubscriptionsRepository.findOne({ + where: { id: dto.userSubscriptionId, user: { id: userId } }, + relations: { + user: true, + plan: { service: true }, + }, + }); + + if (!userSubscription) { + throw new BadRequestException(SubscriptionMessage.NOT_FOUND); + } + + // Calculate remaining days + const remainingDays = this.calculateRemainingDays(userSubscription); + + // Extract duration from current plan and convert to months + const planDurationInMonths = Math.round(userSubscription.plan.duration / 30); + + // Get premium pricing based on extracted duration + const premiumPrice = this.getPremiumPrice(planDurationInMonths); + + // Create invoice items for premium purchase + const invoiceItems = [{ + name: `Premium Plan - ${planDurationInMonths} Months`, + count: 1, + unitPrice: premiumPrice.toNumber(), + discount: 0, + }]; + + // Create invoice directly + const createInvoiceDto = { + userId: userSubscription.user.id, + items: invoiceItems, + isRecurring: false, + }; + + const invoice = await this.invoicesService.createInvoiceAdmin(createInvoiceDto, { + ip: '', + headers: {}, + userId: userSubscription.user.id + }); + + return { + message: "Premium plan purchased successfully", + userSubscription, + invoice, + remainingDays, + premiumPrice: premiumPrice.toNumber(), + }; + } + + //************************************ */ + private calculateRemainingDays(userSubscription: UserSubscription): number { + const now = dayjs(); + const endDate = dayjs(userSubscription.endDate); + + if (endDate.isBefore(now)) { + return 0; + } + + return endDate.diff(now, 'day'); + } + + //************************************ */ + private getPremiumPrice(durationInMonths: number): Decimal { + switch (durationInMonths) { + case 6: + return new Decimal(5000000); + case 12: + return new Decimal(10000000); + default: + throw new BadRequestException("Invalid premium plan duration. Only 6 and 12 month plans are supported."); + } + } +} diff --git a/src/modules/dmenu/providers/restaurant.service.ts b/src/modules/dmenu/providers/restaurant.service.ts index 5ab58a0..3f9c6bd 100644 --- a/src/modules/dmenu/providers/restaurant.service.ts +++ b/src/modules/dmenu/providers/restaurant.service.ts @@ -1,5 +1,5 @@ import { HttpService } from "@nestjs/axios"; -import { Injectable, Logger } from "@nestjs/common"; +import { BadRequestException, Injectable, Logger } from "@nestjs/common"; import { Inject } from "@nestjs/common"; import { AxiosError } from "axios"; import { catchError, firstValueFrom, throwError } from "rxjs"; @@ -8,6 +8,9 @@ import { IDmenuConfig } from "../../../configs/icons.config"; import { DMENU_CONFIG } from "../constants"; import { FindRestaurantsDto } from "../DTO/find-restaurants.dto"; import { CreateRestaurantDto } from "../DTO/create-restaurant.dto"; +import { PlanEnum } from "../interface/plan.interface"; +import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service"; +import { SubscriptionMessage } from "../../../common/enums/message.enum"; @Injectable() export class RestaurantService { @@ -16,6 +19,7 @@ export class RestaurantService { constructor( @Inject(DMENU_CONFIG) private readonly config: IDmenuConfig, private readonly httpService: HttpService, + private readonly subscriptionService: SubscriptionsService, ) {} private getHeaders(): Record { @@ -67,11 +71,18 @@ export class RestaurantService { } } - async createRestaurant(dto: CreateRestaurantDto) { + async createRestaurant(dto: CreateRestaurantDto, userId: string) { + const { userSubscription } = await this.subscriptionService.getUserSubscriptionById(dto.userSubscriptionId, userId); + if (!userSubscription) { + throw new BadRequestException(SubscriptionMessage.USER_SUBS_NOT_FOUND); + } try { const { data } = await firstValueFrom( this.httpService - .post(`${this.config.baseUrl}/super-admin/restaurants`, dto, { + .post(`${this.config.baseUrl}/super-admin/restaurants`, { + ...dto, + plan: PlanEnum.Base, + }, { headers: this.getHeaders(), }) .pipe( @@ -87,5 +98,20 @@ export class RestaurantService { throw error; } } + + async getRestaurantSubscription(subscriptionId: string) { + // TODO: Implement the business logic to get restaurant subscription + // This could involve querying the database, making external API calls, etc. + + this.logger.log(`Getting restaurant subscription for ID: ${subscriptionId}`); + + // For now, return a placeholder response + // You should implement the actual logic based on your business requirements + return { + subscriptionId, + message: "Restaurant subscription endpoint created successfully", + // Add actual data structure based on your requirements + }; + } } diff --git a/src/modules/dmenu/super-admin.controller.ts b/src/modules/dmenu/super-admin.controller.ts new file mode 100644 index 0000000..c355b2f --- /dev/null +++ b/src/modules/dmenu/super-admin.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Param } from "@nestjs/common"; +import { ApiBearerAuth, ApiOperation, ApiParam, ApiTags } from "@nestjs/swagger"; + +import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; +import { PermissionsDec } from "../../common/decorators/permission.decorator"; +import { PermissionEnum } from "../users/enums/permission.enum"; +import { RestaurantService } from "./providers/restaurant.service"; + +@ApiTags("super-admin") +@Controller("super-admin") +@AuthGuards() +@PermissionsDec(PermissionEnum.DMENU) +@ApiBearerAuth() +export class SuperAdminController { + constructor( + private readonly restaurantService: RestaurantService, + ) {} + + @Get('restaurants/subscription/:subscriptionId') + @ApiOperation({ summary: "Get restaurant subscription by subscription ID" }) + @ApiParam({ name: "subscriptionId", required: true, type: String }) + getRestaurantSubscription(@Param("subscriptionId") subscriptionId: string) { + return this.restaurantService.getRestaurantSubscription(subscriptionId); + } +}