refactor cart
This commit is contained in:
@@ -62,7 +62,7 @@ export class DeliveryController {
|
|||||||
@ApiOperation({ summary: 'Get a shop delivery method by delivery ID' })
|
@ApiOperation({ summary: 'Get a shop delivery method by delivery ID' })
|
||||||
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
|
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
|
||||||
findOne(@Param('deliveryId') deliveryId: string, @ShopId() restId: string) {
|
findOne(@Param('deliveryId') deliveryId: string, @ShopId() restId: string) {
|
||||||
return this.deliveryService.findOne(restId, deliveryId);
|
return this.deliveryService.findOrFail(restId, deliveryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
export enum DeliveryMethodEnum {
|
export enum DeliveryMethodEnum {
|
||||||
DineIn = 'dineIn',
|
|
||||||
CustomerPickup = 'customerPickup',
|
CustomerPickup = 'customerPickup',
|
||||||
DeliveryCar = 'deliveryCar',
|
|
||||||
DeliveryCourier = 'deliveryCourier',
|
DeliveryCourier = 'deliveryCourier',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export class DeliveryService {
|
|||||||
private readonly deliveryRepository: DeliveryRepository,
|
private readonly deliveryRepository: DeliveryRepository,
|
||||||
private readonly shopRepository: ShopRepository,
|
private readonly shopRepository: ShopRepository,
|
||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
async create(restId: string, createDto: CreateDeliveryDto): Promise<Delivery> {
|
async create(restId: string, createDto: CreateDeliveryDto): Promise<Delivery> {
|
||||||
const shop = await this.shopRepository.findOne({ id: restId });
|
const shop = await this.shopRepository.findOne({ id: restId });
|
||||||
@@ -54,7 +54,7 @@ export class DeliveryService {
|
|||||||
return this.deliveryRepository.find({ shop: { id: restId } }, { orderBy: { order: 'asc' } });
|
return this.deliveryRepository.find({ shop: { id: restId } }, { orderBy: { order: 'asc' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(restId: string, deliveryId: string): Promise<Delivery> {
|
async findOrFail(restId: string, deliveryId: string): Promise<Delivery> {
|
||||||
const delivery = await this.deliveryRepository.findOne({
|
const delivery = await this.deliveryRepository.findOne({
|
||||||
id: deliveryId,
|
id: deliveryId,
|
||||||
shop: { id: restId },
|
shop: { id: restId },
|
||||||
|
|||||||
@@ -44,9 +44,6 @@ export class Order extends BaseEntity {
|
|||||||
|
|
||||||
@Property({ type: 'json', nullable: true })
|
@Property({ type: 'json', nullable: true })
|
||||||
userAddress?: OrderUserAddress | null;
|
userAddress?: OrderUserAddress | null;
|
||||||
// for car delivery
|
|
||||||
@Property({ type: 'json', nullable: true })
|
|
||||||
carAddress?: OrderCarAddress | null;
|
|
||||||
|
|
||||||
@ManyToOne(() => PaymentMethod)
|
@ManyToOne(() => PaymentMethod)
|
||||||
paymentMethod: PaymentMethod;
|
paymentMethod: PaymentMethod;
|
||||||
@@ -84,8 +81,6 @@ export class Order extends BaseEntity {
|
|||||||
@Property({ type: 'text', nullable: true })
|
@Property({ type: 'text', nullable: true })
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
@Property({ nullable: true })
|
|
||||||
tableNumber?: string;
|
|
||||||
|
|
||||||
@Enum(() => OrderStatus)
|
@Enum(() => OrderStatus)
|
||||||
status!: OrderStatus;
|
status!: OrderStatus;
|
||||||
|
|||||||
@@ -11,19 +11,12 @@ export interface OrderUserAddress {
|
|||||||
phone: string;
|
phone: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderCarAddress {
|
|
||||||
carModel: string;
|
|
||||||
carColor: string;
|
|
||||||
plateNumber: string;
|
|
||||||
phone: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum OrderStatus {
|
export enum OrderStatus {
|
||||||
PENDING_PAYMENT = 'pendingPayment',
|
PENDING_PAYMENT = 'pendingPayment',
|
||||||
PAID = 'paid',
|
PAID = 'paid',
|
||||||
PREPARING = 'preparing',
|
PREPARING = 'preparing',
|
||||||
DELIVERED_TO_WAITER = 'deliveredToWaiter',
|
DELIVERED = 'delivered',
|
||||||
DELIVERED_TO_RECEPTIONIST = 'deliveredToReceptionist',
|
|
||||||
SHIPPED = 'shipped',
|
SHIPPED = 'shipped',
|
||||||
COMPLETED = 'completed',
|
COMPLETED = 'completed',
|
||||||
CANCELED = 'canceled',
|
CANCELED = 'canceled',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { User } from '../../users/entities/user.entity';
|
|||||||
import { Shop } from '../../shops/entities/shop.entity';
|
import { Shop } from '../../shops/entities/shop.entity';
|
||||||
import { Product } from '../../products/entities/product.entity';
|
import { Product } from '../../products/entities/product.entity';
|
||||||
import { CartService } from '../../cart/providers/cart.service';
|
import { CartService } from '../../cart/providers/cart.service';
|
||||||
import { OrderStatus, OrderUserAddress, OrderCarAddress } from '../interface/order.interface';
|
import { OrderStatus, OrderUserAddress } from '../interface/order.interface';
|
||||||
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment';
|
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment';
|
||||||
import { Cart } from '../../cart/interfaces/cart.interface';
|
import { Cart } from '../../cart/interfaces/cart.interface';
|
||||||
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
||||||
@@ -21,6 +21,11 @@ import { StatusTransitionRef } from '../interface/order.interface';
|
|||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.events';
|
import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.events';
|
||||||
import { OrderMessage } from 'src/common/enums/message.enum';
|
import { OrderMessage } from 'src/common/enums/message.enum';
|
||||||
|
import { UserService } from 'src/modules/users/providers/user.service';
|
||||||
|
import { ShopService } from 'src/modules/shops/providers/shops.service';
|
||||||
|
import { DeliveryService } from 'src/modules/delivery/providers/delivery.service';
|
||||||
|
import { PaymentMethodService } from 'src/modules/payments/services/payment-method.service';
|
||||||
|
|
||||||
type OrderItemData = { product: Product; quantity: number; unitPrice: number; discount: number };
|
type OrderItemData = { product: Product; quantity: number; unitPrice: number; discount: number };
|
||||||
|
|
||||||
type ValidatedCartForOrder = {
|
type ValidatedCartForOrder = {
|
||||||
@@ -28,7 +33,6 @@ type ValidatedCartForOrder = {
|
|||||||
shop: Shop;
|
shop: Shop;
|
||||||
delivery: Delivery;
|
delivery: Delivery;
|
||||||
userAddress: OrderUserAddress | null;
|
userAddress: OrderUserAddress | null;
|
||||||
carAddress: OrderCarAddress | null;
|
|
||||||
paymentMethod: PaymentMethod;
|
paymentMethod: PaymentMethod;
|
||||||
orderItemsData: OrderItemData[];
|
orderItemsData: OrderItemData[];
|
||||||
};
|
};
|
||||||
@@ -40,9 +44,8 @@ export class OrdersService {
|
|||||||
private static readonly STATUS_TRANSITIONS: Record<OrderStatus, readonly OrderStatus[]> = {
|
private static readonly STATUS_TRANSITIONS: Record<OrderStatus, readonly OrderStatus[]> = {
|
||||||
[OrderStatus.PENDING_PAYMENT]: [OrderStatus.PAID, OrderStatus.CANCELED, OrderStatus.PREPARING],
|
[OrderStatus.PENDING_PAYMENT]: [OrderStatus.PAID, OrderStatus.CANCELED, OrderStatus.PREPARING],
|
||||||
[OrderStatus.PAID]: [OrderStatus.PREPARING, OrderStatus.CANCELED],
|
[OrderStatus.PAID]: [OrderStatus.PREPARING, OrderStatus.CANCELED],
|
||||||
[OrderStatus.PREPARING]: [OrderStatus.DELIVERED_TO_RECEPTIONIST, OrderStatus.DELIVERED_TO_WAITER, OrderStatus.SHIPPED, OrderStatus.CANCELED],
|
[OrderStatus.PREPARING]: [OrderStatus.DELIVERED, OrderStatus.SHIPPED, OrderStatus.CANCELED],
|
||||||
[OrderStatus.DELIVERED_TO_WAITER]: [OrderStatus.COMPLETED, OrderStatus.CANCELED],
|
[OrderStatus.DELIVERED]: [OrderStatus.COMPLETED, OrderStatus.CANCELED],
|
||||||
[OrderStatus.DELIVERED_TO_RECEPTIONIST]: [OrderStatus.COMPLETED, OrderStatus.CANCELED],
|
|
||||||
[OrderStatus.SHIPPED]: [OrderStatus.COMPLETED, OrderStatus.CANCELED],
|
[OrderStatus.SHIPPED]: [OrderStatus.COMPLETED, OrderStatus.CANCELED],
|
||||||
[OrderStatus.COMPLETED]: [OrderStatus.CANCELED],
|
[OrderStatus.COMPLETED]: [OrderStatus.CANCELED],
|
||||||
[OrderStatus.CANCELED]: [],
|
[OrderStatus.CANCELED]: [],
|
||||||
@@ -52,13 +55,17 @@ export class OrdersService {
|
|||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
private readonly cartService: CartService,
|
private readonly cartService: CartService,
|
||||||
private readonly orderRepository: OrderRepository,
|
private readonly orderRepository: OrderRepository,
|
||||||
|
private readonly shopService: ShopService,
|
||||||
|
private readonly deliveryService: DeliveryService,
|
||||||
|
private readonly paymentMethodService: PaymentMethodService,
|
||||||
private readonly paymentsService: PaymentsService,
|
private readonly paymentsService: PaymentsService,
|
||||||
private readonly eventEmitter: EventEmitter2,
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
private readonly userService: UserService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async checkout(userId: string, restaurantId: string) {
|
async checkout(userId: string, shopId: string) {
|
||||||
const cart = await this.cartService.findOneOrFail(userId, restaurantId);
|
const cart = await this.cartService.findOneOrFail(userId, shopId);
|
||||||
const validated = await this.validateCartForOrder(userId, restaurantId, cart);
|
const validated = await this.validateCartForOrder(userId, shopId, cart);
|
||||||
|
|
||||||
const order = await this.em.transactional(async em => {
|
const order = await this.em.transactional(async em => {
|
||||||
const order = em.create(Order, {
|
const order = em.create(Order, {
|
||||||
@@ -66,7 +73,6 @@ export class OrdersService {
|
|||||||
shop: validated.shop,
|
shop: validated.shop,
|
||||||
deliveryMethod: validated.delivery,
|
deliveryMethod: validated.delivery,
|
||||||
userAddress: validated.userAddress,
|
userAddress: validated.userAddress,
|
||||||
carAddress: validated.carAddress,
|
|
||||||
paymentMethod: validated.paymentMethod,
|
paymentMethod: validated.paymentMethod,
|
||||||
couponDiscount: cart.couponDiscount || 0,
|
couponDiscount: cart.couponDiscount || 0,
|
||||||
couponDetail: cart.coupon,
|
couponDetail: cart.coupon,
|
||||||
@@ -78,7 +84,6 @@ export class OrdersService {
|
|||||||
total: cart.total || 0,
|
total: cart.total || 0,
|
||||||
totalItems: cart.totalItems || 0,
|
totalItems: cart.totalItems || 0,
|
||||||
description: cart.description,
|
description: cart.description,
|
||||||
tableNumber: cart.tableNumber,
|
|
||||||
status: OrderStatus.PENDING_PAYMENT,
|
status: OrderStatus.PENDING_PAYMENT,
|
||||||
history: [{ status: OrderStatus.PENDING_PAYMENT, changedAt: new Date() }],
|
history: [{ status: OrderStatus.PENDING_PAYMENT, changedAt: new Date() }],
|
||||||
});
|
});
|
||||||
@@ -113,16 +118,16 @@ export class OrdersService {
|
|||||||
|
|
||||||
em.persist(payment);
|
em.persist(payment);
|
||||||
await em.flush();
|
await em.flush();
|
||||||
this.logger.debug(`Order ${order.id} created for user ${userId} (shop ${restaurantId})`);
|
this.logger.debug(`Order ${order.id} created for user ${userId} (shop ${shopId})`);
|
||||||
return order;
|
return order;
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.cartService.clearCart(userId, restaurantId);
|
await this.cartService.clearCart(userId, shopId);
|
||||||
|
|
||||||
const { paymentUrl } = await this.paymentsService.payOrder(order.id);
|
const { paymentUrl } = await this.paymentsService.payOrder(order.id);
|
||||||
this.eventEmitter.emit(
|
this.eventEmitter.emit(
|
||||||
OrderCreatedEvent.name,
|
OrderCreatedEvent.name,
|
||||||
new OrderCreatedEvent(order.id, restaurantId, String(order?.orderNumber) || '', order.total),
|
new OrderCreatedEvent(order.id, shopId, String(order?.orderNumber) || '', order.total),
|
||||||
);
|
);
|
||||||
|
|
||||||
return { paymentUrl, order };
|
return { paymentUrl, order };
|
||||||
@@ -131,24 +136,24 @@ export class OrdersService {
|
|||||||
/**
|
/**
|
||||||
* Validates cart and prepares all required data for order creation
|
* Validates cart and prepares all required data for order creation
|
||||||
*/
|
*/
|
||||||
private async validateCartForOrder(userId: string, restaurantId: string, cart: Cart): Promise<ValidatedCartForOrder> {
|
private async validateCartForOrder(userId: string, shopId: string, cart: Cart): Promise<ValidatedCartForOrder> {
|
||||||
this.assertCartHasItems(cart);
|
this.assertCartHasItems(cart);
|
||||||
this.assertCartHasDeliveryMethod(cart);
|
this.assertCartHasDeliveryMethod(cart);
|
||||||
this.assertCartHasPaymentMethod(cart);
|
this.assertCartHasPaymentMethod(cart);
|
||||||
|
|
||||||
const [user, shop, delivery] = await Promise.all([
|
const [user, shop, delivery] = await Promise.all([
|
||||||
this.getUserOrFail(userId),
|
this.userService.findOneOrFail(userId),
|
||||||
this.getRestaurantOrFail(restaurantId),
|
this.shopService.findOrFail(shopId),
|
||||||
this.getDeliveryOrFail(cart.deliveryMethodId!),
|
this.deliveryService.findOrFail(shopId, cart.deliveryMethodId!),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.assertMeetsMinOrderForDelivery(cart, delivery);
|
this.assertMeetsMinOrderForDelivery(cart, delivery);
|
||||||
this.assertDeliveryMethodRequirements(cart, delivery);
|
this.assertDeliveryMethodRequirements(cart, delivery);
|
||||||
|
|
||||||
const paymentMethod = await this.getPaymentMethodOrFail(cart.paymentMethodId!, restaurantId);
|
const paymentMethod = await this.paymentMethodService.findOneOrFail(cart.paymentMethodId!);
|
||||||
this.assertPaymentMethodEnabled(paymentMethod);
|
this.assertPaymentMethodEnabled(paymentMethod);
|
||||||
|
|
||||||
const orderItemsData = await this.buildOrderItemsData(cart, restaurantId);
|
const orderItemsData = await this.buildOrderItemsData(cart, shopId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
@@ -156,13 +161,12 @@ export class OrdersService {
|
|||||||
delivery,
|
delivery,
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
userAddress: delivery.method === DeliveryMethodEnum.DeliveryCourier ? (cart?.userAddress ?? null) : null,
|
userAddress: delivery.method === DeliveryMethodEnum.DeliveryCourier ? (cart?.userAddress ?? null) : null,
|
||||||
carAddress: delivery.method === DeliveryMethodEnum.DeliveryCar ? (cart?.carAddress ?? null) : null,
|
|
||||||
orderItemsData,
|
orderItemsData,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllForUser(restId: string, dto: FindOrdersDto, userId: string): Promise<PaginatedResult<Order>> {
|
async findAllForUser(shopId: string, dto: FindOrdersDto, userId: string): Promise<PaginatedResult<Order>> {
|
||||||
const result = await this.orderRepository.findAllPaginated(restId, {
|
const result = await this.orderRepository.findAllPaginated(shopId, {
|
||||||
page: dto.page,
|
page: dto.page,
|
||||||
limit: dto.limit,
|
limit: dto.limit,
|
||||||
statuses: dto.statuses,
|
statuses: dto.statuses,
|
||||||
@@ -178,8 +182,8 @@ export class OrdersService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllForAdmin(restId: string, dto: FindOrdersDto): Promise<PaginatedResult<Order>> {
|
async findAllForAdmin(shopId: string, dto: FindOrdersDto): Promise<PaginatedResult<Order>> {
|
||||||
const result = await this.orderRepository.findAllPaginated(restId, {
|
const result = await this.orderRepository.findAllPaginated(shopId, {
|
||||||
page: dto.page,
|
page: dto.page,
|
||||||
limit: dto.limit,
|
limit: dto.limit,
|
||||||
statuses: dto.statuses,
|
statuses: dto.statuses,
|
||||||
@@ -195,16 +199,15 @@ export class OrdersService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string, restId: string): Promise<Order> {
|
async findOne(id: string, shopId: string): Promise<Order> {
|
||||||
const order = await this.orderRepository.findOne(
|
const order = await this.orderRepository.findOne(
|
||||||
{ id, shop: { id: restId } },
|
{ id, shop: { id: shopId } },
|
||||||
{
|
{
|
||||||
populate: [
|
populate: [
|
||||||
'user',
|
'user',
|
||||||
'shop',
|
'shop',
|
||||||
'deliveryMethod',
|
'deliveryMethod',
|
||||||
'userAddress',
|
'userAddress',
|
||||||
'carAddress',
|
|
||||||
'paymentMethod',
|
'paymentMethod',
|
||||||
'payments',
|
'payments',
|
||||||
'items',
|
'items',
|
||||||
@@ -219,15 +222,14 @@ export class OrdersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async changeOrderStatus(
|
async changeOrderStatus(
|
||||||
orderId: string,
|
orderId: string,
|
||||||
restId: string,
|
shopId: string,
|
||||||
toStatus: OrderStatus,
|
toStatus: OrderStatus,
|
||||||
ref: StatusTransitionRef,
|
ref: StatusTransitionRef,
|
||||||
desc?: string,
|
desc?: string,
|
||||||
): Promise<Order> {
|
): Promise<Order> {
|
||||||
const order = await this.getOrderOrFail(orderId, restId);
|
const order = await this.getOrderOrFail(orderId, shopId);
|
||||||
|
|
||||||
// Store previous status before changing it
|
// Store previous status before changing it
|
||||||
const previousStatus = order.status;
|
const previousStatus = order.status;
|
||||||
@@ -244,7 +246,7 @@ export class OrdersService {
|
|||||||
orderId,
|
orderId,
|
||||||
order.user?.id || '',
|
order.user?.id || '',
|
||||||
String(order?.orderNumber) || '',
|
String(order?.orderNumber) || '',
|
||||||
restId,
|
shopId,
|
||||||
previousStatus,
|
previousStatus,
|
||||||
toStatus,
|
toStatus,
|
||||||
ref,
|
ref,
|
||||||
@@ -297,15 +299,8 @@ export class OrdersService {
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
from == OrderStatus.PREPARING &&
|
from == OrderStatus.PREPARING &&
|
||||||
to == OrderStatus.DELIVERED_TO_WAITER &&
|
to == OrderStatus.DELIVERED &&
|
||||||
deliveryMethod !== DeliveryMethodEnum.DineIn &&
|
deliveryMethod !== DeliveryMethodEnum.DeliveryCourier &&
|
||||||
deliveryMethod !== DeliveryMethodEnum.DeliveryCar
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
from == OrderStatus.PREPARING &&
|
|
||||||
to == OrderStatus.DELIVERED_TO_RECEPTIONIST &&
|
|
||||||
deliveryMethod !== DeliveryMethodEnum.CustomerPickup
|
deliveryMethod !== DeliveryMethodEnum.CustomerPickup
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
@@ -318,10 +313,10 @@ export class OrdersService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOrderOrFail(orderId: string, restId: string): Promise<Order> {
|
private async getOrderOrFail(orderId: string, shopId: string): Promise<Order> {
|
||||||
const order = await this.em.findOne(
|
const order = await this.em.findOne(
|
||||||
Order,
|
Order,
|
||||||
{ id: orderId, shop: { id: restId } },
|
{ id: orderId, shop: { id: shopId } },
|
||||||
{ populate: ['paymentMethod', 'deliveryMethod', 'user'] },
|
{ populate: ['paymentMethod', 'deliveryMethod', 'user'] },
|
||||||
);
|
);
|
||||||
if (!order) throw new NotFoundException(OrderMessage.NOT_FOUND);
|
if (!order) throw new NotFoundException(OrderMessage.NOT_FOUND);
|
||||||
@@ -346,23 +341,6 @@ export class OrdersService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getUserOrFail(userId: string): Promise<User> {
|
|
||||||
const user = await this.em.findOne(User, { id: userId });
|
|
||||||
if (!user) throw new NotFoundException(OrderMessage.USER_NOT_FOUND);
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getRestaurantOrFail(restaurantId: string): Promise<Shop> {
|
|
||||||
const shop = await this.em.findOne(Shop, { id: restaurantId });
|
|
||||||
if (!shop) throw new NotFoundException(OrderMessage.SHOP_NOT_FOUND);
|
|
||||||
return shop;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getDeliveryOrFail(deliveryId: string): Promise<Delivery> {
|
|
||||||
const delivery = await this.em.findOne(Delivery, { id: deliveryId });
|
|
||||||
if (!delivery) throw new NotFoundException(OrderMessage.DELIVERY_NOT_FOUND);
|
|
||||||
return delivery;
|
|
||||||
}
|
|
||||||
|
|
||||||
private assertMeetsMinOrderForDelivery(cart: Cart, delivery: Delivery) {
|
private assertMeetsMinOrderForDelivery(cart: Cart, delivery: Delivery) {
|
||||||
const minOrderPrice = Number(delivery.minOrderPrice) || 0;
|
const minOrderPrice = Number(delivery.minOrderPrice) || 0;
|
||||||
@@ -372,36 +350,10 @@ export class OrdersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private assertDeliveryMethodRequirements(cart: Cart, delivery: Delivery) {
|
private assertDeliveryMethodRequirements(cart: Cart, delivery: Delivery) {
|
||||||
if (delivery.method === DeliveryMethodEnum.DineIn) {
|
|
||||||
if (!cart.tableNumber || cart.tableNumber.trim() === '') {
|
|
||||||
throw new BadRequestException(OrderMessage.TABLE_NUMBER_REQUIRED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delivery.method === DeliveryMethodEnum.DeliveryCourier && !cart.userAddress) {
|
if (delivery.method === DeliveryMethodEnum.DeliveryCourier && !cart.userAddress) {
|
||||||
throw new BadRequestException(OrderMessage.ADDRESS_REQUIRED);
|
throw new BadRequestException(OrderMessage.ADDRESS_REQUIRED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (delivery.method === DeliveryMethodEnum.DeliveryCar && !cart.carAddress) {
|
|
||||||
throw new BadRequestException(OrderMessage.CAR_ADDRESS_REQUIRED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getPaymentMethodOrFail(paymentMethodId: string, restaurantId: string): Promise<PaymentMethod> {
|
|
||||||
const paymentMethod = await this.em.findOne(
|
|
||||||
PaymentMethod,
|
|
||||||
{
|
|
||||||
id: paymentMethodId,
|
|
||||||
shop: { id: restaurantId },
|
|
||||||
},
|
|
||||||
{ populate: ['shop'] },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!paymentMethod) {
|
|
||||||
throw new NotFoundException(`Payment method with ID ${paymentMethodId} not found for shop ${restaurantId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return paymentMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private assertPaymentMethodEnabled(paymentMethod: PaymentMethod) {
|
private assertPaymentMethodEnabled(paymentMethod: PaymentMethod) {
|
||||||
@@ -410,14 +362,14 @@ export class OrdersService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async buildOrderItemsData(cart: Cart, restaurantId: string): Promise<OrderItemData[]> {
|
private async buildOrderItemsData(cart: Cart, shopId: string): Promise<OrderItemData[]> {
|
||||||
const orderItemsData: OrderItemData[] = [];
|
const orderItemsData: OrderItemData[] = [];
|
||||||
|
|
||||||
for (const cartItem of cart.items) {
|
for (const cartItem of cart.items) {
|
||||||
const product = await this.em.findOne(Product, { id: cartItem.foodId }, { populate: ['shop'] });
|
const product = await this.em.findOne(Product, { id: cartItem.foodId }, { populate: ['shop'] });
|
||||||
if (!product) throw new NotFoundException(OrderMessage.PRODUCT_NOT_FOUND);
|
if (!product) throw new NotFoundException(OrderMessage.PRODUCT_NOT_FOUND);
|
||||||
|
|
||||||
if (product.shop.id !== restaurantId) {
|
if (product.shop.id !== shopId) {
|
||||||
throw new BadRequestException(OrderMessage.PRODUCT_NOT_BELONGS_TO_SHOP);
|
throw new BadRequestException(OrderMessage.PRODUCT_NOT_BELONGS_TO_SHOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,20 +383,17 @@ export class OrdersService {
|
|||||||
|
|
||||||
return orderItemsData;
|
return orderItemsData;
|
||||||
}
|
}
|
||||||
|
// Charts
|
||||||
|
async getStats(shopId: string) {
|
||||||
|
|
||||||
async getStats(restId: string) {
|
|
||||||
return this.em.transactional(async em => {
|
return this.em.transactional(async em => {
|
||||||
// 1. Total orders count (excluding pending and canceled)
|
// 1. Total orders count (excluding pending and canceled)
|
||||||
const totalOrders = await em.count(Order, {
|
const totalOrders = await em.count(Order, {
|
||||||
shop: { id: restId },
|
shop: { id: shopId },
|
||||||
status: {
|
status: {
|
||||||
$in: [
|
$in: [
|
||||||
OrderStatus.PAID,
|
OrderStatus.PAID,
|
||||||
OrderStatus.PREPARING,
|
OrderStatus.PREPARING,
|
||||||
OrderStatus.DELIVERED_TO_WAITER,
|
OrderStatus.DELIVERED,
|
||||||
OrderStatus.DELIVERED_TO_RECEPTIONIST,
|
|
||||||
OrderStatus.SHIPPED,
|
OrderStatus.SHIPPED,
|
||||||
OrderStatus.COMPLETED,
|
OrderStatus.COMPLETED,
|
||||||
],
|
],
|
||||||
@@ -453,7 +402,7 @@ export class OrdersService {
|
|||||||
|
|
||||||
// 2. Active products count
|
// 2. Active products count
|
||||||
const activeFoods = await em.count(Product, {
|
const activeFoods = await em.count(Product, {
|
||||||
shop: { id: restId },
|
shop: { id: shopId },
|
||||||
isActive: true,
|
isActive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -464,7 +413,7 @@ export class OrdersService {
|
|||||||
FROM orders o
|
FROM orders o
|
||||||
WHERE o.restaurant_id = ?
|
WHERE o.restaurant_id = ?
|
||||||
`,
|
`,
|
||||||
[restId],
|
[shopId],
|
||||||
);
|
);
|
||||||
const totalClients = Number(clientsResult[0]?.count || 0);
|
const totalClients = Number(clientsResult[0]?.count || 0);
|
||||||
|
|
||||||
@@ -476,7 +425,7 @@ export class OrdersService {
|
|||||||
INNER JOIN orders o ON p.order_id = o.id
|
INNER JOIN orders o ON p.order_id = o.id
|
||||||
WHERE o.restaurant_id = ? AND p.status = 'paid'
|
WHERE o.restaurant_id = ? AND p.status = 'paid'
|
||||||
`,
|
`,
|
||||||
[restId],
|
[shopId],
|
||||||
);
|
);
|
||||||
const totalRevenue = Number(revenueResult[0]?.total || 0);
|
const totalRevenue = Number(revenueResult[0]?.total || 0);
|
||||||
|
|
||||||
@@ -489,7 +438,7 @@ export class OrdersService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFoodSalesPieChart(restId: string) {
|
async getFoodSalesPieChart(shopId: string) {
|
||||||
return this.em.transactional(async em => {
|
return this.em.transactional(async em => {
|
||||||
// Use last 30 days instead of just last month to be more inclusive
|
// Use last 30 days instead of just last month to be more inclusive
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -505,7 +454,7 @@ export class OrdersService {
|
|||||||
const lastDayOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
|
const lastDayOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Product sales pie chart query params: restId=${restId}, startDate=${startDate.toISOString()}, endDate=${endDate.toISOString()}`,
|
`Product sales pie chart query params: shopId=${shopId}, startDate=${startDate.toISOString()}, endDate=${endDate.toISOString()}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Diagnostic query to check what orders exist
|
// Diagnostic query to check what orders exist
|
||||||
@@ -523,9 +472,9 @@ export class OrdersService {
|
|||||||
ORDER BY o.created_at DESC
|
ORDER BY o.created_at DESC
|
||||||
LIMIT 10
|
LIMIT 10
|
||||||
`,
|
`,
|
||||||
[restId],
|
[shopId],
|
||||||
);
|
);
|
||||||
this.logger.debug(`Diagnostic: Found ${diagnosticResult.length} recent orders for shop ${restId}`);
|
this.logger.debug(`Diagnostic: Found ${diagnosticResult.length} recent orders for shop ${shopId}`);
|
||||||
if (diagnosticResult.length > 0) {
|
if (diagnosticResult.length > 0) {
|
||||||
this.logger.debug(`Sample order: status=${diagnosticResult[0].status}, created_at=${diagnosticResult[0].created_at}`);
|
this.logger.debug(`Sample order: status=${diagnosticResult[0].status}, created_at=${diagnosticResult[0].created_at}`);
|
||||||
}
|
}
|
||||||
@@ -549,7 +498,7 @@ export class OrdersService {
|
|||||||
GROUP BY f.id, f.title
|
GROUP BY f.id, f.title
|
||||||
ORDER BY total_revenue DESC
|
ORDER BY total_revenue DESC
|
||||||
`,
|
`,
|
||||||
[restId, startDate, endDate],
|
[shopId, startDate, endDate],
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.debug(`Product sales pie chart query returned ${result.length} rows`);
|
this.logger.debug(`Product sales pie chart query returned ${result.length} rows`);
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export class PaymentsController {
|
|||||||
@ApiOperation({ summary: 'Get shop payment method by ID' })
|
@ApiOperation({ summary: 'Get shop payment method by ID' })
|
||||||
@ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' })
|
@ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' })
|
||||||
findOneRestaurantPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) {
|
findOneRestaurantPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) {
|
||||||
return this.paymentMethodService.findOne(paymentMethodId);
|
return this.paymentMethodService.findOneOrFail(paymentMethodId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export class PaymentMethodService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly paymentMethodRepository: PaymentMethodRepository,
|
private readonly paymentMethodRepository: PaymentMethodRepository,
|
||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
async create(restId: string, createPaymentMethodDto: CreatePaymentMethodDto): Promise<PaymentMethod> {
|
async create(restId: string, createPaymentMethodDto: CreatePaymentMethodDto): Promise<PaymentMethod> {
|
||||||
// Check if shop exists
|
// Check if shop exists
|
||||||
@@ -34,7 +34,7 @@ export class PaymentMethodService {
|
|||||||
return this.paymentMethodRepository.findAll({ populate: ['shop'] });
|
return this.paymentMethodRepository.findAll({ populate: ['shop'] });
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string): Promise<PaymentMethod> {
|
async findOneOrFail(id: string): Promise<PaymentMethod> {
|
||||||
const paymentMethod = await this.paymentMethodRepository.findOne({ id }, { populate: ['shop'] });
|
const paymentMethod = await this.paymentMethodRepository.findOne({ id }, { populate: ['shop'] });
|
||||||
if (!paymentMethod) {
|
if (!paymentMethod) {
|
||||||
throw new NotFoundException(PaymentMessage.PAYMENT_METHOD_NOT_FOUND);
|
throw new NotFoundException(PaymentMessage.PAYMENT_METHOD_NOT_FOUND);
|
||||||
@@ -47,14 +47,14 @@ export class PaymentMethodService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, updatePaymentMethodDto: UpdatePaymentMethodDto): Promise<PaymentMethod> {
|
async update(id: string, updatePaymentMethodDto: UpdatePaymentMethodDto): Promise<PaymentMethod> {
|
||||||
const paymentMethod = await this.findOne(id);
|
const paymentMethod = await this.findOneOrFail(id);
|
||||||
this.em.assign(paymentMethod, updatePaymentMethodDto);
|
this.em.assign(paymentMethod, updatePaymentMethodDto);
|
||||||
await this.em.flush();
|
await this.em.flush();
|
||||||
return paymentMethod;
|
return paymentMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: string): Promise<PaymentMethod> {
|
async remove(id: string): Promise<PaymentMethod> {
|
||||||
const paymentMethod = await this.findOne(id);
|
const paymentMethod = await this.findOneOrFail(id);
|
||||||
await this.em.removeAndFlush(paymentMethod);
|
await this.em.removeAndFlush(paymentMethod);
|
||||||
return paymentMethod;
|
return paymentMethod;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -385,4 +385,11 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findOneOrFail(userId: string): Promise<User> {
|
||||||
|
const user = await this.userRepository.findOne({ id: userId });
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundException(`User with ID ${userId} not found.`);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user