This commit is contained in:
2025-12-02 20:01:07 +03:30
parent e43727f1c6
commit 40442686be
24 changed files with 420 additions and 490 deletions
+2 -2
View File
@@ -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: [],
@@ -26,7 +26,7 @@ export interface Cart {
totalDiscount: number;
subTotal: number;
tax: number;
deliveryFee: number;
shipmentFee: number;
total: number;
totalItems: number;
createdAt: string;
+10 -10
View File
@@ -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' &&
@@ -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);
}
}
+16
View File
@@ -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 {}
@@ -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;
}
@@ -0,0 +1,5 @@
import { PartialType } from '@nestjs/swagger';
import { CreateDeliveryDto } from './create-delivery.dto';
export class UpdateDeliveryDto extends PartialType(CreateDeliveryDto) {}
@@ -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;
}
@@ -0,0 +1,6 @@
export enum DeliveryName {
DineIn = 'dineIn',
Pickup = 'pickup',
DeliveryCar = 'deliveryCar',
DeliveryCourier = 'deliveryCourier',
}
@@ -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<Delivery> {
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<Delivery> = {
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<Delivery[]> {
return this.deliveryRepository.find({ restaurant: { id: restId } }, { orderBy: { order: 'asc' } });
}
async findOne(restId: string, deliveryId: string): Promise<Delivery> {
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<Delivery> {
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<void> {
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);
}
}
@@ -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<ShipmentMethod> {
export class DeliveryRepository extends EntityRepository<Delivery> {
constructor(readonly em: EntityManager) {
super(em, ShipmentMethod);
super(em, Delivery);
}
}
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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,
@@ -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<ShipmentMethod>(this);
@OneToMany(() => Delivery, delivery => delivery.restaurant)
deliveries = new Collection<Delivery>(this);
}
@@ -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);
}
}
@@ -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;
}
@@ -1,5 +0,0 @@
import { PartialType } from '@nestjs/swagger';
import { CreateRestaurantShipmentMethodDto } from './create-restaurant-shipment-method.dto';
export class UpdateRestaurantShipmentMethodDto extends PartialType(CreateRestaurantShipmentMethodDto) {}
@@ -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;
}
@@ -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<Restaurant>(this);
}
@@ -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<RestaurantShipmentMethod> {
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<RestaurantShipmentMethod> = {
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<RestaurantShipmentMethod[]> {
return this.restaurantShipmentMethodRepository.find(
{ restaurant: { id: restId } },
{ populate: ['shipmentMethod'] },
);
}
async findOne(restId: string, restaurantShipmentMethodId: string): Promise<RestaurantShipmentMethod> {
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<RestaurantShipmentMethod> {
const restaurantShipmentMethod = await this.restaurantShipmentMethodRepository.findOne({
restaurant: { id: restId },
id: restaurantShipmentMethodId,
});
if (!restaurantShipmentMethod) {
throw new NotFoundException(
`روش ارسال با شناسه ${restaurantShipmentMethodId} برای رستوران با شناسه ${restId} یافت نشد.`,
);
}
const updateData: Partial<RestaurantShipmentMethod> = {};
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<void> {
const restaurantShipmentMethod = await this.restaurantShipmentMethodRepository.findOne({
restaurant: { id: restId },
id: restaurantShipmentMethodId,
});
if (!restaurantShipmentMethod) {
throw new NotFoundException(
`روش ارسال با شناسه ${restaurantShipmentMethodId} برای رستوران با شناسه ${restId} یافت نشد.`,
);
}
await this.em.removeAndFlush(restaurantShipmentMethod);
}
}
@@ -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 });
}
}
@@ -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<RestaurantShipmentMethod> {
constructor(readonly em: EntityManager) {
super(em, RestaurantShipmentMethod);
}
}
-23
View File
@@ -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 {}
+72 -61
View File
@@ -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);
}
}
}
}