From 40442686be0105140279279bdd024ea4b975d4e5 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 2 Dec 2025 20:01:07 +0330 Subject: [PATCH] delivery --- src/app.module.ts | 4 +- src/modules/cart/interfaces/cart.interface.ts | 2 +- src/modules/cart/providers/cart.service.ts | 20 +-- .../controllers/delivery.controller.ts | 110 +++++++++++++ src/modules/delivery/delivery.module.ts | 16 ++ .../delivery/dto/create-delivery.dto.ts | 44 +++++ .../delivery/dto/update-delivery.dto.ts | 5 + .../delivery/entities/delivery.entity.ts | 31 ++++ src/modules/delivery/interface/delivery.ts | 6 + .../delivery/providers/delivery.service.ts | 112 +++++++++++++ .../repositories/delivery.repository.ts} | 9 +- src/modules/orders/entities/order.entity.ts | 2 +- src/modules/orders/orders.service.ts | 2 +- .../restaurants/entities/restaurant.entity.ts | 13 +- .../controllers/shipment.controller.ts | 117 -------------- .../create-restaurant-shipment-method.dto.ts | 28 ---- .../update-restaurant-shipment-method.dto.ts | 5 - .../restaurant-shipment-method.entity.ts | 23 --- .../entities/shipment-method.entity.ts | 22 --- .../restaurant-shipment-method.service.ts | 152 ------------------ .../providers/shipment-methods.service.ts | 21 --- .../restaurant-shipment-method.repository.ts | 10 -- src/modules/shipments/shipments.module.ts | 23 --- src/seeders/DatabaseSeeder.ts | 133 ++++++++------- 24 files changed, 420 insertions(+), 490 deletions(-) create mode 100644 src/modules/delivery/controllers/delivery.controller.ts create mode 100644 src/modules/delivery/delivery.module.ts create mode 100644 src/modules/delivery/dto/create-delivery.dto.ts create mode 100644 src/modules/delivery/dto/update-delivery.dto.ts create mode 100644 src/modules/delivery/entities/delivery.entity.ts create mode 100644 src/modules/delivery/interface/delivery.ts create mode 100644 src/modules/delivery/providers/delivery.service.ts rename src/modules/{shipments/repositories/shipment-method.repository.ts => delivery/repositories/delivery.repository.ts} (50%) delete mode 100644 src/modules/shipments/controllers/shipment.controller.ts delete mode 100644 src/modules/shipments/dto/create-restaurant-shipment-method.dto.ts delete mode 100644 src/modules/shipments/dto/update-restaurant-shipment-method.dto.ts delete mode 100644 src/modules/shipments/entities/restaurant-shipment-method.entity.ts delete mode 100644 src/modules/shipments/entities/shipment-method.entity.ts delete mode 100644 src/modules/shipments/providers/restaurant-shipment-method.service.ts delete mode 100644 src/modules/shipments/providers/shipment-methods.service.ts delete mode 100644 src/modules/shipments/repositories/restaurant-shipment-method.repository.ts delete mode 100644 src/modules/shipments/shipments.module.ts diff --git a/src/app.module.ts b/src/app.module.ts index a368c0d..cdf8c4a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -19,7 +19,7 @@ import { FoodModule } from './modules/foods/food.module'; import { CartModule } from './modules/cart/cart.module'; import { RolesModule } from './modules/roles/roles.module'; import { PaymentsModule } from './modules/payments/payments.module'; -import { ShipmentsModule } from './modules/shipments/shipments.module'; +import { DeliveryModule } from './modules/delivery/delivery.module'; import { OrdersModule } from './modules/orders/orders.module'; @Module({ @@ -53,7 +53,7 @@ import { OrdersModule } from './modules/orders/orders.module'; CartModule, RolesModule, PaymentsModule, - ShipmentsModule, + DeliveryModule, OrdersModule, ], controllers: [], diff --git a/src/modules/cart/interfaces/cart.interface.ts b/src/modules/cart/interfaces/cart.interface.ts index 2d031b1..6d7cf5e 100644 --- a/src/modules/cart/interfaces/cart.interface.ts +++ b/src/modules/cart/interfaces/cart.interface.ts @@ -26,7 +26,7 @@ export interface Cart { totalDiscount: number; subTotal: number; tax: number; - deliveryFee: number; + shipmentFee: number; total: number; totalItems: number; createdAt: string; diff --git a/src/modules/cart/providers/cart.service.ts b/src/modules/cart/providers/cart.service.ts index d8983af..e51c699 100644 --- a/src/modules/cart/providers/cart.service.ts +++ b/src/modules/cart/providers/cart.service.ts @@ -45,7 +45,7 @@ export class CartService { restaurantId, restaurantName: restaurant.name, items: [], - deliveryFee: 0, + shipmentFee: 0, subTotal: 0, itemsDiscount: 0, couponDiscount: 0, @@ -449,7 +449,7 @@ export class CartService { } cart.paymentMethodId = setPaymentMethodDto.paymentMethodId; - // cart.deliveryFee = Number(restaurantPaymentMethod.price) || 0; + // cart.shipmentFee = Number(restaurantPaymentMethod.price) || 0; // Recalculate cart totals to include delivery fee await this.recalculateCartTotals(cart); @@ -505,22 +505,22 @@ export class CartService { cart.tax = tax; - // Get delivery fee if payment method is set - let deliveryFee = 0; + // Get shipment fee if payment method is set + let shipmentFee = 0; // if (cart.paymentMethodId) { // const restaurantPaymentMethod = await this.em.findOne(RestaurantPaymentMethod, { // restaurant: { id: cart.restaurantId }, // paymentMethod: { id: cart.paymentMethodId }, // }); // if (restaurantPaymentMethod) { - // deliveryFee = Number(restaurantPaymentMethod.price) || 0; - // deliveryFee = 1; + // shipmentFee = Number(restaurantPaymentMethod.price) || 0; + // shipmentFee = 1; // } // } - cart.deliveryFee = deliveryFee; + cart.shipmentFee = shipmentFee; - // Calculate total: total = subtotal – totalDiscount + tax + deliveryFee - cart.total = subTotal - cart.totalDiscount + tax + deliveryFee; + // Calculate total: total = subtotal – totalDiscount + tax + shipmentFee + cart.total = subTotal - cart.totalDiscount + tax + shipmentFee; cart.totalItems = totalItems; cart.updatedAt = new Date().toISOString(); } @@ -542,7 +542,7 @@ export class CartService { typeof cart.couponDiscount === 'number' && typeof cart.totalDiscount === 'number' && typeof cart.tax === 'number' && - typeof cart.deliveryFee === 'number' && + typeof cart.shipmentFee === 'number' && typeof cart.total === 'number' && typeof cart.totalItems === 'number' && typeof cart.createdAt === 'string' && diff --git a/src/modules/delivery/controllers/delivery.controller.ts b/src/modules/delivery/controllers/delivery.controller.ts new file mode 100644 index 0000000..7cb9f0f --- /dev/null +++ b/src/modules/delivery/controllers/delivery.controller.ts @@ -0,0 +1,110 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common'; +import { + ApiTags, + ApiOperation, + ApiCreatedResponse, + ApiOkResponse, + ApiNotFoundResponse, + ApiParam, + ApiBody, + ApiBearerAuth, +} from '@nestjs/swagger'; +import { DeliveryService } from '../providers/delivery.service'; +import { CreateDeliveryDto } from '../dto/create-delivery.dto'; +import { UpdateDeliveryDto } from '../dto/update-delivery.dto'; +import { RestId } from 'src/common/decorators/rest-id.decorator'; +import { AuthGuard } from 'src/modules/auth/guards/auth.guard'; +import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; +import { Delivery } from '../entities/delivery.entity'; + +@ApiTags('Delivery') +@Controller() +export class DeliveryController { + constructor(private readonly deliveryService: DeliveryService) {} + + @UseGuards(AuthGuard) + @ApiBearerAuth() + @Get('public/delivery-methods/restaurant') + @ApiOperation({ summary: 'Get restaurant delivery methods' }) + @ApiOkResponse({ + description: 'List of restaurant delivery methods', + type: [Delivery], + }) + findAllDeliveryMethods(@RestId() restId: string) { + return this.deliveryService.findAllForRestaurantId(restId); + } + + @Get('admin/delivery-methods') + @ApiOperation({ summary: 'Get all delivery method names' }) + @ApiOkResponse({ + description: 'List of delivery method names', + }) + findAll() { + return this.deliveryService.findAll(); + } + + @UseGuards(AdminAuthGuard) + @ApiBearerAuth() + @Post('admin/delivery-methods/restaurant') + @ApiOperation({ summary: 'Create a delivery method for a restaurant' }) + @ApiBody({ type: CreateDeliveryDto }) + @ApiCreatedResponse({ + description: 'Delivery method created successfully', + type: Delivery, + }) + create(@Body() createDto: CreateDeliveryDto, @RestId() restId: string) { + return this.deliveryService.create(restId, createDto); + } + + @UseGuards(AdminAuthGuard) + @ApiBearerAuth() + @Get('admin/delivery-methods/restaurant') + @ApiOperation({ summary: 'Get the restaurant delivery methods' }) + @ApiOkResponse({ + description: 'List of restaurant delivery methods', + type: [Delivery], + }) + findRestaurantDeliveryMethods(@RestId() restId: string) { + return this.deliveryService.findAllForRestaurantId(restId); + } + + @UseGuards(AdminAuthGuard) + @ApiBearerAuth() + @Get('admin/delivery-methods/restaurant/:deliveryId') + @ApiOperation({ summary: 'Get a restaurant delivery method by delivery ID' }) + @ApiParam({ name: 'deliveryId', description: 'Delivery method ID' }) + @ApiOkResponse({ + description: 'Delivery method details', + type: Delivery, + }) + @ApiNotFoundResponse({ description: 'Delivery method not found' }) + findOne(@Param('deliveryId') deliveryId: string, @RestId() restId: string) { + return this.deliveryService.findOne(restId, deliveryId); + } + + @UseGuards(AdminAuthGuard) + @ApiBearerAuth() + @Patch('admin/delivery-methods/restaurant/:deliveryId') + @ApiOperation({ summary: 'Update a restaurant delivery method' }) + @ApiParam({ name: 'deliveryId', description: 'Delivery method ID' }) + @ApiBody({ type: UpdateDeliveryDto }) + @ApiOkResponse({ + description: 'Delivery method updated successfully', + type: Delivery, + }) + @ApiNotFoundResponse({ description: 'Delivery method not found' }) + update(@Param('deliveryId') deliveryId: string, @Body() updateDto: UpdateDeliveryDto, @RestId() restId: string) { + return this.deliveryService.update(restId, deliveryId, updateDto); + } + + @UseGuards(AdminAuthGuard) + @ApiBearerAuth() + @Delete('admin/delivery-methods/restaurant/:deliveryId') + @ApiOperation({ summary: 'Delete a restaurant delivery method' }) + @ApiParam({ name: 'deliveryId', description: 'Delivery method ID' }) + @ApiOkResponse({ description: 'Delivery method deleted successfully' }) + @ApiNotFoundResponse({ description: 'Delivery method not found' }) + remove(@Param('deliveryId') deliveryId: string, @RestId() restId: string) { + return this.deliveryService.remove(restId, deliveryId); + } +} diff --git a/src/modules/delivery/delivery.module.ts b/src/modules/delivery/delivery.module.ts new file mode 100644 index 0000000..bdf933a --- /dev/null +++ b/src/modules/delivery/delivery.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { JwtModule } from '@nestjs/jwt'; +import { DeliveryController } from './controllers/delivery.controller'; +import { DeliveryService } from './providers/delivery.service'; +import { RestaurantsModule } from '../restaurants/restaurants.module'; +import { Delivery } from './entities/delivery.entity'; +import { DeliveryRepository } from './repositories/delivery.repository'; + +@Module({ + controllers: [DeliveryController], + providers: [DeliveryService, DeliveryRepository], + exports: [DeliveryService, DeliveryRepository], + imports: [MikroOrmModule.forFeature([Delivery]), JwtModule, RestaurantsModule], +}) +export class DeliveryModule {} diff --git a/src/modules/delivery/dto/create-delivery.dto.ts b/src/modules/delivery/dto/create-delivery.dto.ts new file mode 100644 index 0000000..7787373 --- /dev/null +++ b/src/modules/delivery/dto/create-delivery.dto.ts @@ -0,0 +1,44 @@ +import { IsNumber, IsBoolean, IsOptional, IsString, Min, IsEnum } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { DeliveryName } from '../interface/delivery'; + +export class CreateDeliveryDto { + @ApiProperty({ example: 'dineIn', description: 'Delivery method name', enum: DeliveryName }) + @IsEnum(DeliveryName) + name!: DeliveryName; + + @ApiProperty({ example: 'سرو در رستوران', description: 'Delivery method title' }) + @IsString() + title!: string; + + @ApiProperty({ example: 5000, description: 'Delivery fee' }) + @IsNumber() + @Min(0) + @Type(() => Number) + deliveryFee!: number; + + @ApiProperty({ example: 100000, description: 'Minimum order price for free delivery' }) + @IsNumber() + @Min(0) + @Type(() => Number) + minOrderPrice!: number; + + @ApiPropertyOptional({ example: 'توضیحات روش ارسال', description: 'Delivery method description' }) + @IsOptional() + @IsString() + description?: string; + + @ApiPropertyOptional({ example: true, description: 'Is this delivery method enabled?' }) + @IsOptional() + @IsBoolean() + @Type(() => Boolean) + enabled?: boolean; + + @ApiPropertyOptional({ example: 0, description: 'Display order for sorting delivery methods' }) + @IsOptional() + @IsNumber() + @Type(() => Number) + order?: number; +} + diff --git a/src/modules/delivery/dto/update-delivery.dto.ts b/src/modules/delivery/dto/update-delivery.dto.ts new file mode 100644 index 0000000..a98a002 --- /dev/null +++ b/src/modules/delivery/dto/update-delivery.dto.ts @@ -0,0 +1,5 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateDeliveryDto } from './create-delivery.dto'; + +export class UpdateDeliveryDto extends PartialType(CreateDeliveryDto) {} + diff --git a/src/modules/delivery/entities/delivery.entity.ts b/src/modules/delivery/entities/delivery.entity.ts new file mode 100644 index 0000000..e167e08 --- /dev/null +++ b/src/modules/delivery/entities/delivery.entity.ts @@ -0,0 +1,31 @@ +import { Entity, Property, Enum, ManyToOne } from '@mikro-orm/core'; +import { BaseEntity } from '../../../common/entities/base.entity'; +import { Restaurant } from '../../restaurants/entities/restaurant.entity'; +import { DeliveryName } from '../interface/delivery'; + +@Entity({ tableName: 'deliveries' }) +export class Delivery extends BaseEntity { + @Property() + title!: string; + + @Enum(() => DeliveryName) + name!: DeliveryName; + + @ManyToOne(() => Restaurant) + restaurant!: Restaurant; + + @Property({ type: 'decimal', precision: 10, scale: 0, default: 0 }) + deliveryFee: number = 0; + + @Property({ type: 'decimal', precision: 10, scale: 0, default: 0 }) + minOrderPrice: number = 0; + + @Property({ nullable: true }) + description?: string; + + @Property({ default: true }) + enabled: boolean = true; + + @Property({ type: 'integer', default: 0 }) + order: number = 0; +} diff --git a/src/modules/delivery/interface/delivery.ts b/src/modules/delivery/interface/delivery.ts new file mode 100644 index 0000000..ff4e9e7 --- /dev/null +++ b/src/modules/delivery/interface/delivery.ts @@ -0,0 +1,6 @@ +export enum DeliveryName { + DineIn = 'dineIn', + Pickup = 'pickup', + DeliveryCar = 'deliveryCar', + DeliveryCourier = 'deliveryCourier', +} diff --git a/src/modules/delivery/providers/delivery.service.ts b/src/modules/delivery/providers/delivery.service.ts new file mode 100644 index 0000000..c992336 --- /dev/null +++ b/src/modules/delivery/providers/delivery.service.ts @@ -0,0 +1,112 @@ +import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; +import { EntityManager, RequiredEntityData } from '@mikro-orm/postgresql'; +import { Delivery } from '../entities/delivery.entity'; +import { DeliveryRepository } from '../repositories/delivery.repository'; +import { CreateDeliveryDto } from '../dto/create-delivery.dto'; +import { UpdateDeliveryDto } from '../dto/update-delivery.dto'; +import { RestRepository } from '../../restaurants/repositories/rest.repository'; +import { DeliveryName } from '../interface/delivery'; + +@Injectable() +export class DeliveryService { + constructor( + private readonly deliveryRepository: DeliveryRepository, + private readonly restRepository: RestRepository, + private readonly em: EntityManager, + ) {} + + async create(restId: string, createDto: CreateDeliveryDto): Promise { + const restaurant = await this.restRepository.findOne({ id: restId }); + if (!restaurant) { + throw new NotFoundException(`رستوران با شناسه ${restId} یافت نشد.`); + } + + // Check if the delivery method with the same name already exists for this restaurant + const existing = await this.deliveryRepository.findOne({ + restaurant: { id: restId }, + name: createDto.name, + }); + + if (existing) { + throw new ConflictException('این روش ارسال قبلاً برای این رستوران ثبت شده است.'); + } + + const data: RequiredEntityData = { + restaurant, + name: createDto.name, + title: createDto.title, + deliveryFee: createDto.deliveryFee, + minOrderPrice: createDto.minOrderPrice, + description: createDto.description, + enabled: createDto.enabled ?? true, + order: createDto.order ?? 0, + }; + + const delivery = this.deliveryRepository.create(data); + await this.em.persistAndFlush(delivery); + return delivery; + } + + async findAllForRestaurantId(restId: string): Promise { + return this.deliveryRepository.find({ restaurant: { id: restId } }, { orderBy: { order: 'asc' } }); + } + + async findOne(restId: string, deliveryId: string): Promise { + const delivery = await this.deliveryRepository.findOne({ + id: deliveryId, + restaurant: { id: restId }, + }); + + if (!delivery) { + throw new NotFoundException(`روش ارسال با شناسه ${deliveryId} برای رستوران با شناسه ${restId} یافت نشد.`); + } + + return delivery; + } + + async update(restId: string, deliveryId: string, updateDto: UpdateDeliveryDto): Promise { + const delivery = await this.deliveryRepository.findOne({ + restaurant: { id: restId }, + id: deliveryId, + }); + + if (!delivery) { + throw new NotFoundException(`روش ارسال با شناسه ${deliveryId} برای رستوران با شناسه ${restId} یافت نشد.`); + } + + // If name is being updated, check for conflicts + if (updateDto.name !== undefined && updateDto.name !== delivery.name) { + const existing = await this.deliveryRepository.findOne({ + restaurant: { id: restId }, + name: updateDto.name, + id: { $ne: deliveryId }, + }); + + if (existing) { + throw new ConflictException('این روش ارسال قبلاً برای این رستوران ثبت شده است.'); + } + } + + this.em.assign(delivery, updateDto); + await this.em.persistAndFlush(delivery); + + return delivery; + } + + async remove(restId: string, deliveryId: string): Promise { + const delivery = await this.deliveryRepository.findOne({ + restaurant: { id: restId }, + id: deliveryId, + }); + + if (!delivery) { + throw new NotFoundException(`روش ارسال با شناسه ${deliveryId} برای رستوران با شناسه ${restId} یافت نشد.`); + } + + await this.em.removeAndFlush(delivery); + } + + findAll(): DeliveryName[] { + return Object.values(DeliveryName); + } +} diff --git a/src/modules/shipments/repositories/shipment-method.repository.ts b/src/modules/delivery/repositories/delivery.repository.ts similarity index 50% rename from src/modules/shipments/repositories/shipment-method.repository.ts rename to src/modules/delivery/repositories/delivery.repository.ts index d711fa7..371686a 100644 --- a/src/modules/shipments/repositories/shipment-method.repository.ts +++ b/src/modules/delivery/repositories/delivery.repository.ts @@ -1,10 +1,11 @@ -import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; import { Injectable } from '@nestjs/common'; -import { ShipmentMethod } from '../entities/shipment-method.entity'; +import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; +import { Delivery } from '../entities/delivery.entity'; @Injectable() -export class ShipmentMethodRepository extends EntityRepository { +export class DeliveryRepository extends EntityRepository { constructor(readonly em: EntityManager) { - super(em, ShipmentMethod); + super(em, Delivery); } } + diff --git a/src/modules/orders/entities/order.entity.ts b/src/modules/orders/entities/order.entity.ts index aebeae8..d07cf55 100644 --- a/src/modules/orders/entities/order.entity.ts +++ b/src/modules/orders/entities/order.entity.ts @@ -44,7 +44,7 @@ export class Order extends BaseEntity { tax: number = 0; @Property({ type: 'decimal', precision: 10, scale: 0, default: 0 }) - deliveryFee: number = 0; + shipmentFee: number = 0; @Property({ type: 'decimal', precision: 10, scale: 0 }) total!: number; diff --git a/src/modules/orders/orders.service.ts b/src/modules/orders/orders.service.ts index 83582c4..b2aca7b 100644 --- a/src/modules/orders/orders.service.ts +++ b/src/modules/orders/orders.service.ts @@ -55,7 +55,7 @@ export class OrdersService { totalDiscount: cart.totalDiscount || 0, subTotal: cart.subTotal || 0, tax: cart.tax || 0, - deliveryFee: cart.deliveryFee || 0, + shipmentFee: cart.shipmentFee || 0, total: cart.total || 0, totalItems: cart.totalItems || 0, status: OrderStatus.Pending, diff --git a/src/modules/restaurants/entities/restaurant.entity.ts b/src/modules/restaurants/entities/restaurant.entity.ts index 4c069f5..5b62f43 100644 --- a/src/modules/restaurants/entities/restaurant.entity.ts +++ b/src/modules/restaurants/entities/restaurant.entity.ts @@ -1,7 +1,6 @@ -import { Collection, Entity, ManyToMany, Property } from '@mikro-orm/core'; +import { Collection, Entity, OneToMany, Property } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; -import { ShipmentMethod } from '../../shipments/entities/shipment-method.entity'; -import { RestaurantShipmentMethod } from '../../shipments/entities/restaurant-shipment-method.entity'; +import { Delivery } from '../../delivery/entities/delivery.entity'; @Entity({ tableName: 'restaurants' }) export class Restaurant extends BaseEntity { @@ -79,10 +78,6 @@ export class Restaurant extends BaseEntity { vat?: number = 0; // --- روش‌های ارسال --- - @ManyToMany({ - entity: () => ShipmentMethod, - pivotEntity: () => RestaurantShipmentMethod, - inversedBy: sm => sm.restaurants, - }) - shipmentMethods = new Collection(this); + @OneToMany(() => Delivery, delivery => delivery.restaurant) + deliveries = new Collection(this); } diff --git a/src/modules/shipments/controllers/shipment.controller.ts b/src/modules/shipments/controllers/shipment.controller.ts deleted file mode 100644 index 8f02502..0000000 --- a/src/modules/shipments/controllers/shipment.controller.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common'; -import { - ApiTags, - ApiOperation, - ApiCreatedResponse, - ApiOkResponse, - ApiNotFoundResponse, - ApiParam, - ApiBody, - ApiBearerAuth, -} from '@nestjs/swagger'; -import { RestaurantShipmentMethodService } from '../providers/restaurant-shipment-method.service'; -import { CreateRestaurantShipmentMethodDto } from '../dto/create-restaurant-shipment-method.dto'; -import { UpdateRestaurantShipmentMethodDto } from '../dto/update-restaurant-shipment-method.dto'; -import { RestaurantShipmentMethod } from '../entities/restaurant-shipment-method.entity'; -import { RestId } from 'src/common/decorators/rest-id.decorator'; -import { AuthGuard } from 'src/modules/auth/guards/auth.guard'; -import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; -import { ShipmentMethodsService } from '../providers/shipment-methods.service'; - -@ApiTags('Shipment') -@Controller() -export class ShipmentController { - constructor( - private readonly restaurantShipmentMethodService: RestaurantShipmentMethodService, - private readonly shipmentMethodsService: ShipmentMethodsService, - ) {} - @UseGuards(AuthGuard) - @ApiBearerAuth() - @Get('public/shipment-methods/restaurant') - @ApiOperation({ summary: 'Get restaurant shipment methods ' }) - @ApiOkResponse({ - description: 'List of restaurant shipment methods', - type: [RestaurantShipmentMethod], - }) - findAllShipmentMethods(@RestId() restId: string) { - return this.restaurantShipmentMethodService.findAllForRestaurantId(restId); - } - - @Get('admin/shipment-methods') - @ApiOperation({ summary: 'Get all shipment methods' }) - @ApiOkResponse({ - description: 'List of shipment methods', - }) - findAll() { - return this.shipmentMethodsService.findAll(); - } - - @UseGuards(AdminAuthGuard) - @ApiBearerAuth() - @Post('admin/shipment-methods/restaurant') - @ApiOperation({ summary: 'Assign a shipment method to a restaurant' }) - @ApiBody({ type: CreateRestaurantShipmentMethodDto }) - @ApiCreatedResponse({ - description: 'Restaurant shipment method created successfully', - type: RestaurantShipmentMethod, - }) - create(@Body() createDto: CreateRestaurantShipmentMethodDto, @RestId() restId: string) { - return this.restaurantShipmentMethodService.create(restId, createDto); - } - - @UseGuards(AdminAuthGuard) - @ApiBearerAuth() - @Get('admin/shipment-methods/restaurant') - @ApiOperation({ summary: 'Get the restaurant shipment methods' }) - @ApiOkResponse({ - description: 'List of restaurant shipment methods', - type: [RestaurantShipmentMethod], - }) - findRestaurantShipmentMethods(@RestId() restId: string) { - return this.restaurantShipmentMethodService.findAllForRestaurantId(restId); - } - - @UseGuards(AdminAuthGuard) - @ApiBearerAuth() - @Get('admin/shipment-methods/restaurant/:RestaurantShipmentMethodId') - @ApiOperation({ summary: 'Get a restaurant shipment method by restaurant shipment method ID' }) - @ApiParam({ name: 'RestaurantShipmentMethodId', description: 'Restaurant Shipment method ID' }) - @ApiOkResponse({ - description: 'Restaurant Shipment method details', - type: RestaurantShipmentMethod, - }) - @ApiNotFoundResponse({ description: 'Restaurant shipment method not found' }) - findOne(@Param('RestaurantShipmentMethodId') RestaurantShipmentMethodId: string, @RestId() restId: string) { - return this.restaurantShipmentMethodService.findOne(restId, RestaurantShipmentMethodId); - } - - @UseGuards(AdminAuthGuard) - @ApiBearerAuth() - @Patch('admin/shipment-methods/restaurant/:RestaurantShipmentMethodId') - @ApiOperation({ summary: 'Update a restaurant shipment method' }) - @ApiParam({ name: 'RestaurantShipmentMethodId', description: 'Restaurant Shipment method ID' }) - @ApiBody({ type: UpdateRestaurantShipmentMethodDto }) - @ApiOkResponse({ - description: 'Restaurant shipment method updated successfully', - type: RestaurantShipmentMethod, - }) - @ApiNotFoundResponse({ description: 'Restaurant shipment method not found' }) - update( - @Param('RestaurantShipmentMethodId') RestaurantShipmentMethodId: string, - @Body() updateDto: UpdateRestaurantShipmentMethodDto, - @RestId() restId: string, - ) { - return this.restaurantShipmentMethodService.update(restId, RestaurantShipmentMethodId, updateDto); - } - - @UseGuards(AdminAuthGuard) - @ApiBearerAuth() - @Delete('admin/shipment-methods/restaurant/:RestaurantShipmentMethodId') - @ApiOperation({ summary: 'Delete a restaurant shipment method' }) - @ApiParam({ name: 'RestaurantShipmentMethodId', description: 'Shipment method ID' }) - @ApiOkResponse({ description: 'Restaurant shipment method deleted successfully' }) - @ApiNotFoundResponse({ description: 'Restaurant shipment method not found' }) - remove(@Param('RestaurantShipmentMethodId') RestaurantShipmentMethodId: string, @RestId() restId: string) { - return this.restaurantShipmentMethodService.remove(restId, RestaurantShipmentMethodId); - } -} diff --git a/src/modules/shipments/dto/create-restaurant-shipment-method.dto.ts b/src/modules/shipments/dto/create-restaurant-shipment-method.dto.ts deleted file mode 100644 index a09a5d7..0000000 --- a/src/modules/shipments/dto/create-restaurant-shipment-method.dto.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { IsNumber, IsBoolean, IsOptional, IsString, Min } from 'class-validator'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; - -export class CreateRestaurantShipmentMethodDto { - @ApiProperty({ example: 'shipment-method-id', description: 'Shipment method ID' }) - @IsString() - shipmentMethodId!: string; - - @ApiProperty({ example: 5000, description: 'Shipping price' }) - @IsNumber() - @Min(0) - @Type(() => Number) - price!: number; - - @ApiProperty({ example: 100000, description: 'Minimum order price for free shipping' }) - @IsNumber() - @Min(0) - @Type(() => Number) - minOrderPrice!: number; - - @ApiPropertyOptional({ example: true, description: 'Is this shipment method active?' }) - @IsOptional() - @IsBoolean() - @Type(() => Boolean) - isActive?: boolean; -} - diff --git a/src/modules/shipments/dto/update-restaurant-shipment-method.dto.ts b/src/modules/shipments/dto/update-restaurant-shipment-method.dto.ts deleted file mode 100644 index 8984f1b..0000000 --- a/src/modules/shipments/dto/update-restaurant-shipment-method.dto.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PartialType } from '@nestjs/swagger'; -import { CreateRestaurantShipmentMethodDto } from './create-restaurant-shipment-method.dto'; - -export class UpdateRestaurantShipmentMethodDto extends PartialType(CreateRestaurantShipmentMethodDto) {} - diff --git a/src/modules/shipments/entities/restaurant-shipment-method.entity.ts b/src/modules/shipments/entities/restaurant-shipment-method.entity.ts deleted file mode 100644 index f3ef3c8..0000000 --- a/src/modules/shipments/entities/restaurant-shipment-method.entity.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Entity, ManyToOne, Property, Unique } from '@mikro-orm/core'; -import { Restaurant } from '../../restaurants/entities/restaurant.entity'; -import { ShipmentMethod } from './shipment-method.entity'; -import { BaseEntity } from 'src/common/entities/base.entity'; - -@Entity({ tableName: 'restaurant_shipment_methods' }) -@Unique({ properties: ['restaurant', 'shipmentMethod'] }) -export class RestaurantShipmentMethod extends BaseEntity { - @ManyToOne(() => Restaurant, { primary: true }) - restaurant!: Restaurant; - - @ManyToOne(() => ShipmentMethod, { primary: true }) - shipmentMethod!: ShipmentMethod; - - @Property({ type: 'decimal', precision: 10, scale: 0, default: 0 }) - price: number = 0; - - @Property({ type: 'decimal', precision: 10, scale: 0, default: 0 }) - minOrderPrice: number = 0; - - @Property({ default: true }) - isActive: boolean = true; -} diff --git a/src/modules/shipments/entities/shipment-method.entity.ts b/src/modules/shipments/entities/shipment-method.entity.ts deleted file mode 100644 index d93c426..0000000 --- a/src/modules/shipments/entities/shipment-method.entity.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Entity, Property, Unique, ManyToMany, Collection } from '@mikro-orm/core'; -import { BaseEntity } from '../../../common/entities/base.entity'; -import { Restaurant } from '../../restaurants/entities/restaurant.entity'; - -@Entity({ tableName: 'shipment_methods' }) -export class ShipmentMethod extends BaseEntity { - @Property() - @Unique() - name!: string; - - @Property() - title!: string; - - @Property({ nullable: true }) - description?: string; - - @Property({ default: true }) - isActive: boolean = true; - - @ManyToMany({ entity: () => Restaurant, mappedBy: 'shipmentMethods' }) - restaurants = new Collection(this); -} diff --git a/src/modules/shipments/providers/restaurant-shipment-method.service.ts b/src/modules/shipments/providers/restaurant-shipment-method.service.ts deleted file mode 100644 index 636a306..0000000 --- a/src/modules/shipments/providers/restaurant-shipment-method.service.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; -import { EntityManager, RequiredEntityData } from '@mikro-orm/postgresql'; -import { RestaurantShipmentMethod } from '../entities/restaurant-shipment-method.entity'; -import { RestaurantShipmentMethodRepository } from '../repositories/restaurant-shipment-method.repository'; -import { CreateRestaurantShipmentMethodDto } from '../dto/create-restaurant-shipment-method.dto'; -import { UpdateRestaurantShipmentMethodDto } from '../dto/update-restaurant-shipment-method.dto'; -import { RestRepository } from '../../restaurants/repositories/rest.repository'; -import { ShipmentMethod } from '../entities/shipment-method.entity'; - -@Injectable() -export class RestaurantShipmentMethodService { - constructor( - private readonly restaurantShipmentMethodRepository: RestaurantShipmentMethodRepository, - private readonly restRepository: RestRepository, - private readonly em: EntityManager, - ) {} - - async create(restId: string, createDto: CreateRestaurantShipmentMethodDto): Promise { - const restaurant = await this.restRepository.findOne({ id: restId }); - if (!restaurant) { - throw new NotFoundException(`رستوران با شناسه ${restId} یافت نشد.`); - } - - const shipmentMethod = await this.em.findOne(ShipmentMethod, { id: createDto.shipmentMethodId }); - if (!shipmentMethod) { - throw new NotFoundException(`روش ارسال با شناسه ${createDto.shipmentMethodId} یافت نشد.`); - } - - // Check if the relationship already exists - const existing = await this.restaurantShipmentMethodRepository.findOne({ - restaurant: { id: restId }, - shipmentMethod: { id: createDto.shipmentMethodId }, - }); - - if (existing) { - throw new ConflictException('این روش ارسال قبلاً برای این رستوران ثبت شده است.'); - } - - const data: RequiredEntityData = { - restaurant, - shipmentMethod, - price: createDto.price, - minOrderPrice: createDto.minOrderPrice, - isActive: createDto.isActive ?? true, - }; - - const restaurantShipmentMethod = this.restaurantShipmentMethodRepository.create(data); - await this.em.persistAndFlush(restaurantShipmentMethod); - return restaurantShipmentMethod; - } - - async findAllForRestaurantId(restId: string): Promise { - return this.restaurantShipmentMethodRepository.find( - { restaurant: { id: restId } }, - { populate: ['shipmentMethod'] }, - ); - } - - async findOne(restId: string, restaurantShipmentMethodId: string): Promise { - const restaurantShipmentMethod = await this.restaurantShipmentMethodRepository.findOne( - { - id: restaurantShipmentMethodId, - restaurant: { id: restId }, - }, - { populate: ['shipmentMethod'] }, - ); - - if (!restaurantShipmentMethod) { - throw new NotFoundException( - `Restaurant shipment method with ID ${restaurantShipmentMethodId} not found for restaurant with ID ${restId}`, - ); - } - - return restaurantShipmentMethod; - } - - async update( - restId: string, - restaurantShipmentMethodId: string, - updateDto: UpdateRestaurantShipmentMethodDto, - ): Promise { - const restaurantShipmentMethod = await this.restaurantShipmentMethodRepository.findOne({ - restaurant: { id: restId }, - id: restaurantShipmentMethodId, - }); - - if (!restaurantShipmentMethod) { - throw new NotFoundException( - `روش ارسال با شناسه ${restaurantShipmentMethodId} برای رستوران با شناسه ${restId} یافت نشد.`, - ); - } - - const updateData: Partial = {}; - - if (updateDto.price !== undefined) { - updateData.price = updateDto.price; - } - if (updateDto.minOrderPrice !== undefined) { - updateData.minOrderPrice = updateDto.minOrderPrice; - } - if (updateDto.isActive !== undefined) { - updateData.isActive = updateDto.isActive; - } - - // If shipmentMethodId is being updated, we need to check for conflicts - if ( - updateDto.shipmentMethodId !== undefined && - updateDto.shipmentMethodId !== restaurantShipmentMethod.shipmentMethod.id - ) { - const newShipmentMethod = await this.em.findOne(ShipmentMethod, { id: updateDto.shipmentMethodId }); - if (!newShipmentMethod) { - throw new NotFoundException(`روش ارسال با شناسه ${updateDto.shipmentMethodId} یافت نشد.`); - } - - // Check if the new relationship already exists - const existing = await this.restaurantShipmentMethodRepository.findOne({ - restaurant: { id: restId }, - shipmentMethod: { id: updateDto.shipmentMethodId }, - id: { $ne: restaurantShipmentMethodId }, - }); - - if (existing) { - throw new ConflictException('این روش ارسال قبلاً برای این رستوران ثبت شده است.'); - } - - updateData.shipmentMethod = newShipmentMethod; - } - - this.em.assign(restaurantShipmentMethod, updateData); - await this.em.persistAndFlush(restaurantShipmentMethod); - - // Reload with populated relations - await this.em.populate(restaurantShipmentMethod, ['shipmentMethod']); - - return restaurantShipmentMethod; - } - - async remove(restId: string, restaurantShipmentMethodId: string): Promise { - const restaurantShipmentMethod = await this.restaurantShipmentMethodRepository.findOne({ - restaurant: { id: restId }, - id: restaurantShipmentMethodId, - }); - - if (!restaurantShipmentMethod) { - throw new NotFoundException( - `روش ارسال با شناسه ${restaurantShipmentMethodId} برای رستوران با شناسه ${restId} یافت نشد.`, - ); - } - - await this.em.removeAndFlush(restaurantShipmentMethod); - } -} diff --git a/src/modules/shipments/providers/shipment-methods.service.ts b/src/modules/shipments/providers/shipment-methods.service.ts deleted file mode 100644 index 67bff0e..0000000 --- a/src/modules/shipments/providers/shipment-methods.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { EntityManager } from '@mikro-orm/postgresql'; - -import { ShipmentMethodRepository } from '../repositories/shipment-method.repository'; - -@Injectable() -export class ShipmentMethodsService { - constructor( - private readonly em: EntityManager, - private readonly shipmentMethodRepository: ShipmentMethodRepository, - ) {} - - findAll() { - return this.shipmentMethodRepository.findAll(); - } - - findOne(id: string) { - return this.shipmentMethodRepository.findOne({ id }); - } -} diff --git a/src/modules/shipments/repositories/restaurant-shipment-method.repository.ts b/src/modules/shipments/repositories/restaurant-shipment-method.repository.ts deleted file mode 100644 index 608113d..0000000 --- a/src/modules/shipments/repositories/restaurant-shipment-method.repository.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; -import { Injectable } from '@nestjs/common'; -import { RestaurantShipmentMethod } from '../entities/restaurant-shipment-method.entity'; - -@Injectable() -export class RestaurantShipmentMethodRepository extends EntityRepository { - constructor(readonly em: EntityManager) { - super(em, RestaurantShipmentMethod); - } -} diff --git a/src/modules/shipments/shipments.module.ts b/src/modules/shipments/shipments.module.ts deleted file mode 100644 index 65d69e6..0000000 --- a/src/modules/shipments/shipments.module.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Module } from '@nestjs/common'; -import { ShipmentMethodsService } from './providers/shipment-methods.service'; -import { ShipmentMethodRepository } from './repositories/shipment-method.repository'; -import { ShipmentMethod } from './entities/shipment-method.entity'; -import { MikroOrmModule } from '@mikro-orm/nestjs'; -import { JwtModule } from '@nestjs/jwt'; -import { ShipmentController } from './controllers/shipment.controller'; -import { RestaurantShipmentMethodService } from './providers/restaurant-shipment-method.service'; -import { RestaurantsModule } from '../restaurants/restaurants.module'; -import { RestaurantShipmentMethodRepository } from './repositories/restaurant-shipment-method.repository'; - -@Module({ - controllers: [ShipmentController], - providers: [ - ShipmentMethodsService, - ShipmentMethodRepository, - RestaurantShipmentMethodRepository, - RestaurantShipmentMethodService, - ], - exports: [ShipmentMethodsService, ShipmentMethodRepository, RestaurantShipmentMethodService], - imports: [MikroOrmModule.forFeature([ShipmentMethod]), JwtModule, RestaurantsModule], -}) -export class ShipmentsModule {} diff --git a/src/seeders/DatabaseSeeder.ts b/src/seeders/DatabaseSeeder.ts index dc313ca..1a1f1ad 100644 --- a/src/seeders/DatabaseSeeder.ts +++ b/src/seeders/DatabaseSeeder.ts @@ -11,8 +11,8 @@ import { User } from '../modules/users/entities/user.entity'; import { Permission, PermissionTitles } from '../common/enums/permission.enum'; import { PaymentMethod } from '../modules/payments/entities/payment-method.entity'; import { RestaurantPaymentMethod } from '../modules/payments/entities/restaurant-payment-method.entity'; -import { ShipmentMethod } from '../modules/shipments/entities/shipment-method.entity'; -import { RestaurantShipmentMethod } from '../modules/shipments/entities/restaurant-shipment-method.entity'; +import { Delivery } from '../modules/delivery/entities/delivery.entity'; +import { DeliveryName } from '../modules/delivery/interface/delivery'; import { Schedule } from '../modules/restaurants/entities/schedule.entity'; export class DatabaseSeeder extends Seeder { @@ -150,52 +150,7 @@ export class DatabaseSeeder extends Seeder { await em.flush(); - // 4. Create Shipment Methods (Farsi) - const shipmentMethods = [ - { - name: 'dine-in', - title: 'سرو در رستوران', - description: 'سرو غذا در رستوران', - isActive: true, - }, - { - name: 'delivery-location', - title: 'تحویل در محل', - description: 'تحویل غذا در محل', - isActive: true, - }, - { - name: 'delivery-car', - title: 'تحویل با ماشین', - description: 'تحویل غذا با ماشین', - isActive: true, - }, - { - name: 'delivery-courier', - title: 'تحویل با پیک', - description: 'تحویل غذا با پیک', - isActive: true, - }, - ]; - - const createdShipmentMethods: ShipmentMethod[] = []; - for (const methodData of shipmentMethods) { - let shipmentMethod = await em.findOne(ShipmentMethod, { name: methodData.name }); - if (!shipmentMethod) { - shipmentMethod = em.create(ShipmentMethod, methodData); - em.persist(shipmentMethod); - } else { - // Update existing method if needed - if (shipmentMethod.title !== methodData.title || shipmentMethod.description !== methodData.description) { - shipmentMethod.title = methodData.title; - shipmentMethod.description = methodData.description; - em.persist(shipmentMethod); - } - } - createdShipmentMethods.push(shipmentMethod); - } - - await em.flush(); + // 4. Delivery methods will be created per restaurant (see section 5.5.5) // 5. Create Restaurants (Farsi) const zhivanRestaurant = await em.findOne(Restaurant, { slug: 'zhivan' }); @@ -276,28 +231,84 @@ export class DatabaseSeeder extends Seeder { await em.flush(); - // 5.5.5. Create Restaurant Shipment Methods - const allRestaurantsForShipments = await em.find(Restaurant, {}); - const allShipmentMethods = await em.find(ShipmentMethod, { isActive: true }); + // 5.5.5. Create Delivery Methods for Restaurants + const allRestaurantsForDeliveries = await em.find(Restaurant, {}); + const deliveryMethodsData = [ + { + name: DeliveryName.DineIn, + title: 'سرو در رستوران', + description: 'سرو غذا در رستوران', + deliveryFee: 0, + minOrderPrice: 0, + enabled: true, + order: 1, + }, + { + name: DeliveryName.Pickup, + title: 'تحویل در رستوران', + description: 'تحویل غذا در محل', + deliveryFee: 0, + minOrderPrice: 0, + enabled: true, + order: 2, + }, + { + name: DeliveryName.DeliveryCar, + title: 'تحویل با ماشین', + description: 'تحویل غذا با ماشین', + deliveryFee: 5000, + minOrderPrice: 50000, + enabled: true, + order: 3, + }, + { + name: DeliveryName.DeliveryCourier, + title: 'تحویل با پیک', + description: 'تحویل غذا با پیک', + deliveryFee: 10000, + minOrderPrice: 100000, + enabled: true, + order: 4, + }, + ]; - for (const restaurant of allRestaurantsForShipments) { + for (const restaurant of allRestaurantsForDeliveries) { if (!restaurant) continue; - for (const shipmentMethod of allShipmentMethods) { - const existing = await em.findOne(RestaurantShipmentMethod, { + for (const methodData of deliveryMethodsData) { + const existing = await em.findOne(Delivery, { restaurant: { id: restaurant.id }, - shipmentMethod: { id: shipmentMethod.id }, + name: methodData.name, }); if (!existing) { - const restaurantShipmentMethod = em.create(RestaurantShipmentMethod, { + const delivery = em.create(Delivery, { restaurant, - shipmentMethod, - isActive: true, - price: 0, - minOrderPrice: 0, + name: methodData.name, + title: methodData.title, + description: methodData.description, + deliveryFee: methodData.deliveryFee, + minOrderPrice: methodData.minOrderPrice, + enabled: methodData.enabled, + order: methodData.order, }); - em.persist(restaurantShipmentMethod); + em.persist(delivery); + } else { + // Update existing delivery method if needed + if ( + existing.title !== methodData.title || + existing.description !== methodData.description || + existing.deliveryFee !== methodData.deliveryFee || + existing.minOrderPrice !== methodData.minOrderPrice || + existing.order !== methodData.order + ) { + existing.title = methodData.title; + existing.description = methodData.description; + existing.deliveryFee = methodData.deliveryFee; + existing.minOrderPrice = methodData.minOrderPrice; + existing.order = methodData.order; + em.persist(existing); + } } } }