From 979e80ded41eb6c9975173979e73738583d02b16 Mon Sep 17 00:00:00 2001 From: Oyinlola Olasunkanmi Raymond <60177090+olasunkanmi-SE@users.noreply.github.com> Date: Sun, 7 Jan 2024 17:42:54 +0800 Subject: [PATCH 1/2] include restaurant information as part of the menu response (#471) (#472) * include restaurant information as part of the menu response * fix build errors * fix build errors --------- Co-authored-by: Olasunkanmi Oyinlola --- .../repositories/restaurant.repository.ts | 5 ++++ backend/src/menu/menu-response.dto.ts | 2 ++ backend/src/menu/menu-service.interface.ts | 1 + backend/src/menu/menu.controller.ts | 4 +++- backend/src/menu/menu.parser.ts | 8 ++++--- backend/src/menu/menu.service.ts | 17 ++++++++++++- backend/src/restaurant/restaurant.mapper.ts | 11 +++++---- backend/src/restaurant/restaurant.parser.ts | 5 ++-- .../src/singleclient/singleclient.service.ts | 1 - frontend/src/apis/orderApi.ts | 13 +++++----- frontend/src/components/Menu/MenuList.tsx | 1 + frontend/src/dto/order.ts | 9 +++++++ frontend/src/utility/utils.ts | 24 +++++++++---------- 13 files changed, 71 insertions(+), 30 deletions(-) create mode 100644 frontend/src/dto/order.ts diff --git a/backend/src/infrastructure/data_access/repositories/restaurant.repository.ts b/backend/src/infrastructure/data_access/repositories/restaurant.repository.ts index 637e8043..8f85df6c 100644 --- a/backend/src/infrastructure/data_access/repositories/restaurant.repository.ts +++ b/backend/src/infrastructure/data_access/repositories/restaurant.repository.ts @@ -8,6 +8,8 @@ import { RestaurantMapper } from './../../../restaurant/restaurant.mapper'; import { SingleClientRepository } from './singleclient.repository'; import { IRestaurantRepository } from './interfaces/restaurant-repository.interface'; import { RestaurantData, RestaurantDocument } from './schemas'; +import { throwApplicationError } from '../../../infrastructure/utilities/exception-instance'; +import { HttpStatus } from '@nestjs/common'; export class RestaurantRepository extends GenericDocumentRepository @@ -27,6 +29,9 @@ export class RestaurantRepository async getRestaurant(restaurantId: Types.ObjectId): Promise { const restaurantDoc = await this.DocumentModel.findById(restaurantId).populate('singleclient').exec(); + if (!restaurantDoc) { + throwApplicationError(HttpStatus.NOT_FOUND, `Restaurant with id ${restaurantId} does not exist`); + } const restaurant: Restaurant = this.restaurantMapper.toDomain(restaurantDoc); return restaurant; } diff --git a/backend/src/menu/menu-response.dto.ts b/backend/src/menu/menu-response.dto.ts index 6617dbf3..1d93d3c5 100644 --- a/backend/src/menu/menu-response.dto.ts +++ b/backend/src/menu/menu-response.dto.ts @@ -1,4 +1,5 @@ import { Types } from 'mongoose'; +import { IRestaurantResponseDTO } from 'src/restaurant'; import { ICategoryResponseDTO } from './../category/category-response.dto'; import { IAudit } from './../infrastructure/database/mongoDB/base-document.interface'; import { ITemResponseDTO } from './../item/item-response.dto'; @@ -12,4 +13,5 @@ export interface IMenuResponseDTO extends IAudit { restaurantId: Types.ObjectId; category: ICategoryResponseDTO; items?: ITemResponseDTO[]; + restaurant?: IRestaurantResponseDTO; } diff --git a/backend/src/menu/menu-service.interface.ts b/backend/src/menu/menu-service.interface.ts index dbd981da..34c1f914 100644 --- a/backend/src/menu/menu-service.interface.ts +++ b/backend/src/menu/menu-service.interface.ts @@ -10,4 +10,5 @@ export interface IMenuService { updateMenu(props: any, id: Types.ObjectId): Promise>; deleteMenu(id: Types.ObjectId): Promise>; getMenuByRestaurantId(restaurantId: string): Promise>; + getExtendedMenuByRestaurantId(restaurantId: string): Promise>; } diff --git a/backend/src/menu/menu.controller.ts b/backend/src/menu/menu.controller.ts index 01be5b1d..d9e01e96 100644 --- a/backend/src/menu/menu.controller.ts +++ b/backend/src/menu/menu.controller.ts @@ -39,8 +39,10 @@ export class MenuController { return this.menuService.deleteMenu(menuId); } + //Todo. + //This API should require both the merchantId and RestaurantId /singleclient/:singleClientId/:restaurantId @Get('/singleclient/:restaurantId') async getMenusByRestaurantId(@Param('restaurantId') restaurantId: string): Promise> { - return this.menuService.getMenuByRestaurantId(restaurantId); + return this.menuService.getExtendedMenuByRestaurantId(restaurantId); } } diff --git a/backend/src/menu/menu.parser.ts b/backend/src/menu/menu.parser.ts index dc2a0b66..0dfb0604 100644 --- a/backend/src/menu/menu.parser.ts +++ b/backend/src/menu/menu.parser.ts @@ -1,3 +1,4 @@ +import { Restaurant, RestaurantParser } from '../restaurant'; import { AuditParser } from '../audit'; import { CategoryParser } from './../category/category.parser'; import { ITemResponseDTO } from './../item/item-response.dto'; @@ -6,7 +7,7 @@ import { Menu } from './menu'; import { IMenuResponseDTO } from './menu-response.dto'; export class MenuParser { - static createMenuResponse(menu: Menu): IMenuResponseDTO { + static createMenuResponse(menu: Menu, restaurant?: Restaurant): IMenuResponseDTO { const { id, name, description, items, audit, discount, imageUrl, basePrice, category, restaurantId } = menu; let itemsResponse: ITemResponseDTO[] = []; if (items?.length) { @@ -20,13 +21,14 @@ export class MenuParser { imageUrl, basePrice, restaurantId, + restaurant: restaurant ? RestaurantParser.createRestaurantResponse(restaurant) : undefined, category: category ? CategoryParser.createCategoryResponse(category) : undefined, items: itemsResponse, ...AuditParser.createAuditResponse(audit), }; } - static createMenusResponse(menus: Menu[]): IMenuResponseDTO[] { - return menus.map((menu) => this.createMenuResponse(menu)); + static createMenusResponse(menus: Menu[], restaurant?: Restaurant): IMenuResponseDTO[] { + return menus.map((menu) => this.createMenuResponse(menu, restaurant)); } } diff --git a/backend/src/menu/menu.service.ts b/backend/src/menu/menu.service.ts index 10360b1c..e1278b3d 100644 --- a/backend/src/menu/menu.service.ts +++ b/backend/src/menu/menu.service.ts @@ -20,6 +20,7 @@ import { IMenuResponseDTO } from './menu-response.dto'; import { IMenuService } from './menu-service.interface'; import { MenuParser } from './menu.parser'; import { UpdateMenuDTO } from './update-menu.schema'; +import { Restaurant } from 'src/restaurant'; @Injectable() export class MenuService implements IMenuService { private context: Context; @@ -126,6 +127,20 @@ export class MenuService implements IMenuService { if (result.getValue()) { menus = result.getValue(); } - return Result.ok(menus && menus.length ? MenuParser.createMenusResponse(menus) : []); + return Result.ok(menus?.length ? MenuParser.createMenusResponse(menus) : []); + } + + async getExtendedMenuByRestaurantId(restaurantId: string): Promise> { + const result = await this.menuRepository.getMenuByRestaurantId(restaurantId); + let menus: Menu[] | []; + if (result.getValue()) { + menus = result.getValue(); + } + const id = menus[0].restaurantId; + let restaurant: Restaurant | undefined; + if (id) { + restaurant = await this.restaurantRepository.getRestaurant(id); + } + return Result.ok(menus?.length ? MenuParser.createMenusResponse(menus, restaurant) : []); } } diff --git a/backend/src/restaurant/restaurant.mapper.ts b/backend/src/restaurant/restaurant.mapper.ts index 3e6d522f..79f0cb0e 100644 --- a/backend/src/restaurant/restaurant.mapper.ts +++ b/backend/src/restaurant/restaurant.mapper.ts @@ -7,6 +7,7 @@ import { RestaurantData } from './../infrastructure/data_access/repositories/sch import { LocationMapper } from './../location/location.mapper'; import { SingleClientMapper } from './../singleclient/singleclient.mapper'; import { Restaurant } from './restaurant'; +import { SingleClient } from 'src/singleclient'; @Injectable() export class RestaurantMapper implements IMapper { @@ -48,7 +49,7 @@ export class RestaurantMapper implements IMapper { paymentMethod, openingHour, closingHour, - menus: menus && menus.length ? menus.map((menu) => this.menuMapper.toPersistence(menu)) : [], + menus: menus?.length ? menus.map((menu) => this.menuMapper.toPersistence(menu)) : [], location: this.locationMapper.toPersistence(entity.location), singleclientId, singleclient: this.singleclientMapper.toPersistence(entity.singleclient), @@ -62,7 +63,7 @@ export class RestaurantMapper implements IMapper { return document; } - toDomain(document: RestaurantData): Restaurant { + toDomain(document: any): Restaurant { const { name, email, @@ -79,6 +80,8 @@ export class RestaurantMapper implements IMapper { closingHour, menus, singleclientId, + singleclient, + location, } = document; const entity: Restaurant = Restaurant.create( { @@ -96,8 +99,8 @@ export class RestaurantMapper implements IMapper { closingHour, singleclientId, menus: menus.length ? menus.map((menu) => this.menuMapper.toDomain(menu)) : [], - location: this.locationMapper.toDomain(document.location), - singleclient: this.singleclientMapper.toDomain(document.singleclient), + location: this.locationMapper.toDomain(location), + singleclient: singleclient ? this.singleclientMapper.toDomain(singleclient) : undefined, audit: this.auditMapper.toDomain(document), }, _id, diff --git a/backend/src/restaurant/restaurant.parser.ts b/backend/src/restaurant/restaurant.parser.ts index f43f287e..838ca904 100644 --- a/backend/src/restaurant/restaurant.parser.ts +++ b/backend/src/restaurant/restaurant.parser.ts @@ -7,6 +7,7 @@ import { IRestaurantResponseDTO } from './restaurant-response.dto'; export class RestaurantParser { static createRestaurantResponse(restaurant: Restaurant): IRestaurantResponseDTO { const { audit, location, singleclient, menus } = restaurant; + const auditResponse = audit ? { ...AuditParser.createAuditResponse(audit) } : undefined; const restaurantResponse: IRestaurantResponseDTO = { id: restaurant.id, name: restaurant.name, @@ -17,8 +18,8 @@ export class RestaurantParser { timeZone: restaurant.timeZone, menus: MenuParser.createMenusResponse(menus), location: LocationParser.createLocationResponse(location), - ...AuditParser.createAuditResponse(audit), - singleclient: SingleClientParser.createSingleClientResponse(singleclient), + ...auditResponse, + singleclient: singleclient ? SingleClientParser.createSingleClientResponse(singleclient) : undefined, }; return restaurantResponse; } diff --git a/backend/src/singleclient/singleclient.service.ts b/backend/src/singleclient/singleclient.service.ts index b0aed825..2d3bccc2 100644 --- a/backend/src/singleclient/singleclient.service.ts +++ b/backend/src/singleclient/singleclient.service.ts @@ -84,7 +84,6 @@ export class SingleClientService extends AuthService implements ISingleClientSer } return Result.ok(SingleClientParser.createSingleClientResponse(singleclient)); } - ß; async getSingleClients(): Promise> { const isValidUser = await this.validateContext(); diff --git a/frontend/src/apis/orderApi.ts b/frontend/src/apis/orderApi.ts index 83ffd5ca..17cab1c5 100644 --- a/frontend/src/apis/orderApi.ts +++ b/frontend/src/apis/orderApi.ts @@ -2,6 +2,7 @@ import { OrderSummary, SelectedItem } from "./../reducers/cartReducer"; import { useShoppingCart } from "../hooks/UseShoppingCart"; import useAxiosPrivate from "../hooks/useAxiosPrivate"; import { IcartItems } from "../models/order.model"; +import { ICreateOrderDTO } from "../dto/order"; const getOrderSummary = () => { const { GetOrderSummary } = useShoppingCart(); @@ -12,17 +13,19 @@ export const createOrder = async () => { return useAxiosPrivate(); }; -const mapOrderSummaryToOrderRequest = () => { +const order = (): ICreateOrderDTO | undefined => { + let order: ICreateOrderDTO | undefined; const orderSummary = getOrderSummary(); if (orderSummary?.length) { - return { + order = { state: "CREATED", type: "DINE_IN", - singleclientId: "63d792433b857e1697fe7017", + singleClientId: "63d792433b857e1697fe7017", total: calculateOrderTotalPrice(orderSummary) ?? 0, cartItems: cartItemsMapper(orderSummary), }; } + return order; }; const calculateOrderTotalPrice = (orderSummary: OrderSummary[]) => { @@ -46,9 +49,7 @@ const cartItemsMapper = (orderSummary: OrderSummary[]): IcartItems[] => { const cartItems = menus.map((menu) => { return { menuId: menu.id, - total: - menu.menuTotalPrice ?? - 0 - calculateCartItemsTotalPrice(menu.selectedItems ?? []), + total: menu.menuTotalPrice ?? 0 - calculateCartItemsTotalPrice(menu.selectedItems ?? []), note: menu.note, selectedItems: menu.selectedItems?.map((item) => { return { ...item, itemId: item.id }; diff --git a/frontend/src/components/Menu/MenuList.tsx b/frontend/src/components/Menu/MenuList.tsx index 2b56295e..ae5098e5 100644 --- a/frontend/src/components/Menu/MenuList.tsx +++ b/frontend/src/components/Menu/MenuList.tsx @@ -9,6 +9,7 @@ export const MenuList = () => { const axiosPrivate = useAxiosPrivate(); const getMenus = async (): Promise => { + //Todo //remove the add coded value and make it dynamic const response = await axiosPrivate.get("/menus/singleclient/63d792433b857e1697fe7017"); return response.data; diff --git a/frontend/src/dto/order.ts b/frontend/src/dto/order.ts new file mode 100644 index 00000000..e01ca347 --- /dev/null +++ b/frontend/src/dto/order.ts @@ -0,0 +1,9 @@ +import { IcartItems } from "../models/order.model"; + +export interface ICreateOrderDTO { + state: string; + type: string; + singleClientId: string; + total: number; + cartItems: IcartItems[]; +} diff --git a/frontend/src/utility/utils.ts b/frontend/src/utility/utils.ts index d1b97481..c87bac09 100644 --- a/frontend/src/utility/utils.ts +++ b/frontend/src/utility/utils.ts @@ -31,31 +31,31 @@ export const calculateTotalOrderAmount = (): number => { }; export const setLocalStorageData = (key: string, value: string, encrypt: boolean) => { - if (encrypt) { - try { + try { + if (encrypt) { const encryptedText = cryptoJs.AES.encrypt(value, import.meta.env.VITE_SECRET); if (encryptedText) { localStorage.setItem(key, encryptedText.toString()); } - } catch (error) { - console.log("Error while saving user Data", error); + } else { + localStorage.setItem(key, value); } - } else { - localStorage.setItem(key, value); + } catch (error) { + console.log("Error while saving user Data", error); } }; export const getLocalStorageData = (key: string, decrypt: boolean) => { - let value = localStorage.getItem(key); - if (value && decrypt) { - try { + try { + let value = localStorage.getItem(key); + if (value && decrypt) { const decryptedText = cryptoJs.AES.decrypt(value, import.meta.env.VITE_SECRET); return decryptedText.toString(cryptoJs.enc.Utf8); - } catch (error) { - console.log("Error while getting user data", error); } + return value; + } catch (error) { + console.log("Error while getting user data", error); } - return value; }; export const clearStorage = () => { From d51e328c0552ddec2db0e9353928dbf88e5ad55d Mon Sep 17 00:00:00 2001 From: Oyinlola Olasunkanmi Raymond <60177090+olasunkanmi-SE@users.noreply.github.com> Date: Sun, 7 Jan 2024 19:14:01 +0800 Subject: [PATCH 2/2] 349 place order (#473) * include restaurant information as part of the menu response * fix build errors * fix build errors * fix the create order flow, from frontend to backend --------- Co-authored-by: Olasunkanmi Oyinlola --- .../models/order-model.interface.ts | 3 +- .../repositories/schemas/order.schema.ts | 6 +- .../mongoDB/generic-document.repository.ts | 6 +- backend/src/order/dto/create-order.dto.ts | 2 +- backend/src/order/order-entity.interface.ts | 3 +- backend/src/order/order-mock-data.ts | 3 +- backend/src/order/order.mapper.ts | 11 +- backend/src/order/order.service.ts | 33 ++++-- backend/src/order/order.ts | 25 ++++- backend/src/order_notes/order_note.service.ts | 6 +- backend/src/restaurant/restaurant.mapper.ts | 2 - frontend/src/apis/orderApi.ts | 100 +++++++++--------- .../components/Cart/ShoppinCartDetails.tsx | 30 +++++- frontend/src/hooks/useAxiosPrivate.ts | 2 +- frontend/src/utility/axios-error-handler.ts | 23 ++++ 15 files changed, 171 insertions(+), 84 deletions(-) create mode 100644 frontend/src/utility/axios-error-handler.ts diff --git a/backend/src/infrastructure/data_access/repositories/models/order-model.interface.ts b/backend/src/infrastructure/data_access/repositories/models/order-model.interface.ts index 3ed7b090..ed47258a 100644 --- a/backend/src/infrastructure/data_access/repositories/models/order-model.interface.ts +++ b/backend/src/infrastructure/data_access/repositories/models/order-model.interface.ts @@ -4,7 +4,7 @@ import { CartItemDataModel } from '../schemas/cartItem.schema'; import { OrderStatusModel } from '../schemas/order-status.schema'; export interface IOrderDataModel { - readonly state: OrderStatusModel; + readonly state?: OrderStatusModel; readonly type: dinningType; readonly singleclientId: Types.ObjectId; readonly customerId?: Types.ObjectId; @@ -12,4 +12,5 @@ export interface IOrderDataModel { readonly discount?: number; readonly orderManagerId?: Types.ObjectId; readonly cartItems?: CartItemDataModel[]; + readonly orderStatusId: Types.ObjectId; } diff --git a/backend/src/infrastructure/data_access/repositories/schemas/order.schema.ts b/backend/src/infrastructure/data_access/repositories/schemas/order.schema.ts index 1afe4462..81f72708 100644 --- a/backend/src/infrastructure/data_access/repositories/schemas/order.schema.ts +++ b/backend/src/infrastructure/data_access/repositories/schemas/order.schema.ts @@ -34,9 +34,13 @@ export class OrderDataModel extends BaseDocument implements IOrderDataModel { @Type(() => CartItemDataModel) cartItems?: CartItemDataModel[]; + @Prop({ type: mongoose.Schema.Types.ObjectId }) + @Type(() => OrderStatusModel) + orderStatusId: Types.ObjectId; + @Prop({ type: { type: mongoose.Schema.Types.ObjectId, ref: OrderStatusModel.name } }) @Type(() => OrderStatusModel) - state: OrderStatusModel; + state?: OrderStatusModel; } export const OrderSchema = SchemaFactory.createForClass(OrderDataModel); diff --git a/backend/src/infrastructure/database/mongoDB/generic-document.repository.ts b/backend/src/infrastructure/database/mongoDB/generic-document.repository.ts index 6e0b0faf..b1ab3349 100644 --- a/backend/src/infrastructure/database/mongoDB/generic-document.repository.ts +++ b/backend/src/infrastructure/database/mongoDB/generic-document.repository.ts @@ -22,10 +22,6 @@ export abstract class GenericDocumentRepository imp private readonly mapper: any, ) {} - public static createObjectId() { - return new Types.ObjectId(); - } - private convertObjectIdToString(objectId: Types.ObjectId) { return objectId.toString(); } @@ -194,7 +190,7 @@ export abstract class GenericDocumentRepository imp createDocument(document: any) { return new this.DocumentModel({ ...document, - _id: GenericDocumentRepository.createObjectId(), + _id: new Types.ObjectId(), }); } } diff --git a/backend/src/order/dto/create-order.dto.ts b/backend/src/order/dto/create-order.dto.ts index f2376918..a8853053 100644 --- a/backend/src/order/dto/create-order.dto.ts +++ b/backend/src/order/dto/create-order.dto.ts @@ -13,7 +13,7 @@ export class CreateOrderDTO { @IsString() @IsNotEmpty() - singleclientId: string; + singleClientId: string; @IsNumber() @IsNotEmpty() diff --git a/backend/src/order/order-entity.interface.ts b/backend/src/order/order-entity.interface.ts index d82157f3..236e04c0 100644 --- a/backend/src/order/order-entity.interface.ts +++ b/backend/src/order/order-entity.interface.ts @@ -7,7 +7,8 @@ export type currentStatus = 'CREATED' | 'ACCEPTED' | 'DENIED' | 'FINISHED' | 'CA export type dinningType = 'PICK_UP' | 'DINE_IN' | 'DELIVERY'; export interface IOrder { - state: OrderStatus; + state?: OrderStatus; + orderStatusId: Types.ObjectId; type: dinningType; singleclientId: Types.ObjectId; customerId?: Types.ObjectId; diff --git a/backend/src/order/order-mock-data.ts b/backend/src/order/order-mock-data.ts index 422a7479..2cf75525 100644 --- a/backend/src/order/order-mock-data.ts +++ b/backend/src/order/order-mock-data.ts @@ -10,6 +10,7 @@ import { IOrder } from './order-entity.interface'; const id = new Types.ObjectId(); export const orderMockData: IOrder = { + orderStatusId: id, state: OrderStatus.create(orderStatusMockData), type: 'DINE_IN', singleclientId: id, @@ -22,7 +23,7 @@ export const orderMockData: IOrder = { export const orderMock: CreateOrderDTO = { state: 'CREATED', type: 'DINE_IN', - singleclientId: id.toString(), + singleClientId: id.toString(), total: 1, cartItems: [createItem], }; diff --git a/backend/src/order/order.mapper.ts b/backend/src/order/order.mapper.ts index 97883fde..a122d848 100644 --- a/backend/src/order/order.mapper.ts +++ b/backend/src/order/order.mapper.ts @@ -14,7 +14,8 @@ export class OrderMapper implements IMapper { private readonly orderStatusMapper: OrderStatusMapper, ) {} toPersistence(entity: Order): OrderDataModel { - const { id, state, type, singleclientId, total, discount, orderManagerId, audit, cartItems } = entity; + const { id, state, type, singleclientId, total, discount, orderManagerId, audit, cartItems, orderStatusId } = + entity; const { auditCreatedBy, auditCreatedDateTime, @@ -25,9 +26,10 @@ export class OrderMapper implements IMapper { } = audit; const orderDocument: OrderDataModel = { _id: id, - state: this.orderStatusMapper.toPersistence(state), + state: state ? this.orderStatusMapper.toPersistence(state) : undefined, type, singleclientId, + orderStatusId, total, discount, cartItems: cartItems?.length ? cartItems.map((cartItem) => this.cartItemMapper.toPersistence(cartItem)) : [], @@ -43,12 +45,13 @@ export class OrderMapper implements IMapper { } toDomain(model: OrderDataModel): Order { - const { state, type, singleclientId, total, discount, orderManagerId, _id, cartItems } = model; + const { state, type, singleclientId, total, discount, orderManagerId, _id, cartItems, orderStatusId } = model; const entity: Order = Order.create( { - state: this.orderStatusMapper.toDomain(state), + state: state ? this.orderStatusMapper.toDomain(state) : undefined, type, singleclientId, + orderStatusId, total, discount, orderManagerId, diff --git a/backend/src/order/order.service.ts b/backend/src/order/order.service.ts index febaaa0f..4b5b8292 100644 --- a/backend/src/order/order.service.ts +++ b/backend/src/order/order.service.ts @@ -48,13 +48,13 @@ export class OrderService implements IOrderService { async createOrder(orderSummary: CreateOrderDTO): Promise> { await this.singleclientService.validateContext(); - const { state, type, singleclientId, total, cartItems } = orderSummary; - const orderDuplicate = await this.orderRepository.getDuplicateOrder(type, singleclientId, cartItems); + const { state, type, singleClientId, total, cartItems } = orderSummary; + const orderDuplicate = await this.orderRepository.getDuplicateOrder(type, singleClientId, cartItems); if (orderDuplicate) { throwApplicationError(HttpStatus.NOT_FOUND, 'Duplicate order detected. Please confirm.'); } const validateSingleClient: Result = await this.singleclientRepository.findOne({ - _id: singleclientId, + _id: singleClientId, }); if (!validateSingleClient.isSuccess) { throwApplicationError(HttpStatus.NOT_FOUND, `SingleClient does not exist`); @@ -63,13 +63,19 @@ export class OrderService implements IOrderService { try { await session.startTransaction(); const audit: Audit = Audit.createInsertContext(this.context); - const singleclientObjId = this.orderRepository.stringToObjectId(singleclientId); + const singleclientObjId = this.orderRepository.stringToObjectId(singleClientId); const getOrderStatus = await this.orderStatusRespository.findOne({ code: state.toUpperCase() }); if (!getOrderStatus) { throwApplicationError(HttpStatus.INTERNAL_SERVER_ERROR, `Order status not found`); } const orderStatus = getOrderStatus.getValue(); - const order: Order = Order.create({ state: orderStatus, type, total, singleclientId: singleclientObjId, audit }); + const order: Order = Order.create({ + orderStatusId: orderStatus.id, + type, + total, + singleclientId: singleclientObjId, + audit, + }); const orderModel: OrderDataModel = this.orderMapper.toPersistence(order); const orderToSave: Result = await this.orderRepository.createOrder(orderModel); const savedOrder = orderToSave.getValue(); @@ -156,13 +162,18 @@ export class OrderService implements IOrderService { async createOrderNotes(cartItems: CreateCartItemsDTO[], orderId: Types.ObjectId): Promise { const orderNotes = cartItems.map(({ menuId, note }: CreateCartItemsDTO) => { - return { - menuId, - note: note || '', - orderId: orderId, - }; + let noteToSave: { menuId: Types.ObjectId; note: string; orderId: Types.ObjectId } | undefined; + if (note?.length) { + noteToSave = { + menuId, + note: note, + orderId: orderId, + }; + } + return noteToSave; }); - const notes = await this.orderNoteService.createNotes(orderNotes); + const notesToSave = orderNotes.filter((note) => note !== undefined); + const notes = await this.orderNoteService.createNotes(notesToSave); return notes.getValue(); } diff --git a/backend/src/order/order.ts b/backend/src/order/order.ts index 24139b5b..c0965014 100644 --- a/backend/src/order/order.ts +++ b/backend/src/order/order.ts @@ -6,9 +6,10 @@ import { Audit } from './../domain/audit/audit'; import { IOrder, dinningType } from './order-entity.interface'; export class Order extends Entity implements IOrder { - _state: OrderStatus; + _state: OrderStatus | undefined; _type: dinningType; _singleclientId: Types.ObjectId; + _orderStatusId: Types.ObjectId; _customerId?: Types.ObjectId; _total: number; _discount?: number; @@ -18,7 +19,18 @@ export class Order extends Entity implements IOrder { constructor( id: Types.ObjectId, - { state, type, singleclientId, customerId, total, discount, orderManagerId, audit, cartItems }: IOrder, + { + state, + type, + singleclientId, + customerId, + total, + discount, + orderManagerId, + audit, + cartItems, + orderStatusId, + }: IOrder, ) { super(id); this._state = state; @@ -30,6 +42,7 @@ export class Order extends Entity implements IOrder { this._orderManagerId = orderManagerId; this._audit = audit; this._cartItems = cartItems; + this._orderStatusId = orderStatusId; } get state(): OrderStatus { @@ -64,6 +77,14 @@ export class Order extends Entity implements IOrder { this._customerId = customerId; } + get orderStatusId(): Types.ObjectId { + return this._orderStatusId; + } + + set orderStatusId(orderStatusId: Types.ObjectId) { + this._orderStatusId = orderStatusId; + } + get total(): number { return this._total; } diff --git a/backend/src/order_notes/order_note.service.ts b/backend/src/order_notes/order_note.service.ts index cb67a006..ee638123 100644 --- a/backend/src/order_notes/order_note.service.ts +++ b/backend/src/order_notes/order_note.service.ts @@ -28,8 +28,10 @@ export class OrderNoteService implements IOrderNoteService { async createNotes(props: CreateOrderNoteDTO[]): Promise> { try { const notes = props.map((note) => { - const noteEntity = this.createOrderNoteEntity(note); - return this.orderNoteMapper.toPersistence(noteEntity); + if (note) { + const noteEntity = this.createOrderNoteEntity(note); + return this.orderNoteMapper.toPersistence(noteEntity); + } }); return this.orderNoteRepository.insertMany(notes); } catch (error) { diff --git a/backend/src/restaurant/restaurant.mapper.ts b/backend/src/restaurant/restaurant.mapper.ts index 79f0cb0e..8ffacf44 100644 --- a/backend/src/restaurant/restaurant.mapper.ts +++ b/backend/src/restaurant/restaurant.mapper.ts @@ -7,8 +7,6 @@ import { RestaurantData } from './../infrastructure/data_access/repositories/sch import { LocationMapper } from './../location/location.mapper'; import { SingleClientMapper } from './../singleclient/singleclient.mapper'; import { Restaurant } from './restaurant'; -import { SingleClient } from 'src/singleclient'; - @Injectable() export class RestaurantMapper implements IMapper { constructor( diff --git a/frontend/src/apis/orderApi.ts b/frontend/src/apis/orderApi.ts index 17cab1c5..9587ed69 100644 --- a/frontend/src/apis/orderApi.ts +++ b/frontend/src/apis/orderApi.ts @@ -1,61 +1,59 @@ import { OrderSummary, SelectedItem } from "./../reducers/cartReducer"; import { useShoppingCart } from "../hooks/UseShoppingCart"; -import useAxiosPrivate from "../hooks/useAxiosPrivate"; import { IcartItems } from "../models/order.model"; import { ICreateOrderDTO } from "../dto/order"; -const getOrderSummary = () => { - const { GetOrderSummary } = useShoppingCart(); - return GetOrderSummary(); -}; - -export const createOrder = async () => { - return useAxiosPrivate(); -}; - -const order = (): ICreateOrderDTO | undefined => { - let order: ICreateOrderDTO | undefined; - const orderSummary = getOrderSummary(); - if (orderSummary?.length) { - order = { - state: "CREATED", - type: "DINE_IN", - singleClientId: "63d792433b857e1697fe7017", - total: calculateOrderTotalPrice(orderSummary) ?? 0, - cartItems: cartItemsMapper(orderSummary), - }; - } - return order; -}; +export const OrderApi = () => { + const getOrderSummary = () => { + const { GetOrderSummary } = useShoppingCart(); + return GetOrderSummary(); + }; -const calculateOrderTotalPrice = (orderSummary: OrderSummary[]) => { - return orderSummary.reduce((acc, item) => { - return acc + (item.menus[0]?.menuTotalPrice ?? 0); - }, 0); -}; + const order = (): ICreateOrderDTO | undefined => { + let order: ICreateOrderDTO | undefined; + const orderSummary = getOrderSummary(); + if (orderSummary?.length) { + order = { + state: "CREATED", + type: "DINE_IN", + singleClientId: "63d78441a6fda119c09b1930", + total: calculateOrderTotalPrice(orderSummary) ?? 0, + cartItems: cartItemsMapper(orderSummary), + }; + } + return order; + }; -const calculateCartItemsTotalPrice = (selectedItems: SelectedItem[]) => { - let totalSelectedItemsPrice = 0; - if (selectedItems?.length) { - totalSelectedItemsPrice = selectedItems.reduce((acc, item) => { - return acc + Number(item?.price ?? 0); + const calculateOrderTotalPrice = (orderSummary: OrderSummary[]) => { + return orderSummary.reduce((acc, item) => { + return acc + (item.menus[0]?.menuTotalPrice ?? 0); }, 0); - } - return totalSelectedItemsPrice; -}; + }; + + const calculateCartItemsTotalPrice = (selectedItems: SelectedItem[]) => { + let totalSelectedItemsPrice = 0; + if (selectedItems?.length) { + totalSelectedItemsPrice = selectedItems.reduce((acc, item) => { + return acc + Number(item?.price ?? 0); + }, 0); + } + return totalSelectedItemsPrice; + }; -const cartItemsMapper = (orderSummary: OrderSummary[]): IcartItems[] => { - const menus = orderSummary.flatMap((summary) => summary.menus); - const cartItems = menus.map((menu) => { - return { - menuId: menu.id, - total: menu.menuTotalPrice ?? 0 - calculateCartItemsTotalPrice(menu.selectedItems ?? []), - note: menu.note, - selectedItems: menu.selectedItems?.map((item) => { - return { ...item, itemId: item.id }; - }), - quantity: menu.quantity, - }; - }); - return cartItems; + const cartItemsMapper = (orderSummary: OrderSummary[]): IcartItems[] => { + const menus = orderSummary.flatMap((summary) => summary.menus); + const cartItems = menus.map((menu) => { + return { + menuId: menu.id, + total: menu.menuTotalPrice ?? 0 - calculateCartItemsTotalPrice(menu.selectedItems ?? []), + note: menu.note, + selectedItems: menu.selectedItems?.map((item) => { + return { ...item, itemId: item.id }; + }), + quantity: menu.quantity, + }; + }); + return cartItems; + }; + return order(); }; diff --git a/frontend/src/components/Cart/ShoppinCartDetails.tsx b/frontend/src/components/Cart/ShoppinCartDetails.tsx index 2ce385e3..603db713 100644 --- a/frontend/src/components/Cart/ShoppinCartDetails.tsx +++ b/frontend/src/components/Cart/ShoppinCartDetails.tsx @@ -10,9 +10,16 @@ import { QtyButton } from "../MenuItems/addItemButton"; import { CallToAction } from "../Utilities/modal"; import { CartSelectedItems } from "./CartSelectedItems"; import { UpgradeShoppingCartItem } from "./ShoppingCartSelectedItemUpdate"; +import { OrderApi } from "../../apis/orderApi"; +import useAxiosPrivate from "../../hooks/useAxiosPrivate"; +import { handleAxiosError } from "../../utility/axios-error-handler"; +import { useMutation } from "react-query"; +import { ICreateOrderDTO } from "../../dto/order"; export const ShoppingCartDetails = () => { const navigate = useNavigate(); + const order = OrderApi(); + const axios = useAxiosPrivate(); const [isEdit, setIsEdit] = useState(false); const { GetOrderSummary, resetCart, closeCart, updateCartItems } = useShoppingCart(); const [showClearCartModal, setShowClearCartModal] = useState(false); @@ -75,6 +82,12 @@ export const ShoppingCartDetails = () => { navigate("/"); }; + const handleCreateOrder: any = useMutation({ + mutationFn: (order: ICreateOrderDTO) => { + return axios.post("orders/create", order); + }, + }); + return (
@@ -183,9 +196,24 @@ export const ShoppingCartDetails = () => {

{calculateServiceCharge(calculateTotalOrderAmount())}

- +
+ {handleCreateOrder.isLoading ? ( + "Creating Order..." + ) : ( + <>{handleCreateOrder.isError ?
An error occurred: {handleCreateOrder.error.message}
: null} + )} + {handleCreateOrder.isSuccess ?
Order processed successfully
: null} +
{showClearCartModal && ( diff --git a/frontend/src/hooks/useAxiosPrivate.ts b/frontend/src/hooks/useAxiosPrivate.ts index 49b95035..283abe0d 100644 --- a/frontend/src/hooks/useAxiosPrivate.ts +++ b/frontend/src/hooks/useAxiosPrivate.ts @@ -7,7 +7,7 @@ export const useAxiosPrivate = () => { const requestInterceptor = axiosPrivate.interceptors.request.use( (config) => { if (!config.headers["x-user-email"]) { - config.headers["x-user-email"] = import.meta.env.GUEST_USER; + config.headers["x-user-email"] = import.meta.env.VITE_GUEST_USER; } if (!config.headers["x-correlation-id"]) { config.headers["x-correlation-id"] = nanoid(); diff --git a/frontend/src/utility/axios-error-handler.ts b/frontend/src/utility/axios-error-handler.ts new file mode 100644 index 00000000..9f781935 --- /dev/null +++ b/frontend/src/utility/axios-error-handler.ts @@ -0,0 +1,23 @@ +import axios from "axios"; + +export const handleAxiosError = (error: any) => { + const { response, message, request } = error; + switch (true) { + case axios.isCancel(error): + console.error("Request canceled:", message); + break; + case error.response: + console.error("Response data:", response.data); + console.error("Response status:", response.status); + console.error("Response headers:", response.headers); + break; + case error.request: + console.error("No response received:", request); + break; + default: + console.error("Error during request setup:", message); + break; + } + console.error("Error config:", error.config); + throw error; +};