From af1269dcc8efc95c29c297371c3a8bb852bbae86 Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Wed, 14 Jan 2026 20:06:20 -0500 Subject: [PATCH 1/4] refactoring backend for request entity having multiple orders --- .../foodRequests/request.controller.spec.ts | 8 +++--- .../src/foodRequests/request.controller.ts | 9 +++--- .../src/foodRequests/request.entity.ts | 4 +-- .../src/foodRequests/request.service.spec.ts | 28 ++++++++++--------- .../src/foodRequests/request.service.ts | 22 +++++++++------ 5 files changed, 40 insertions(+), 31 deletions(-) diff --git a/apps/backend/src/foodRequests/request.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts index 71c35ca4..4e356837 100644 --- a/apps/backend/src/foodRequests/request.controller.spec.ts +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -107,7 +107,7 @@ describe('RequestsController', () => { requestId: 1, ...createBody, requestedAt: new Date(), - order: null, + orders: null, }; mockRequestsService.create.mockResolvedValueOnce( @@ -181,7 +181,7 @@ describe('RequestsController', () => { mockRequestsService.findOne.mockResolvedValue({ requestId, pantryId: 1, - order: { orderId: 99 }, + orders: [{ orderId: 99 }], } as FoodRequest); mockOrdersService.updateStatus.mockResolvedValue(); @@ -230,7 +230,7 @@ describe('RequestsController', () => { mockRequestsService.findOne.mockResolvedValue({ requestId, pantryId: 1, - order: { orderId: 100 }, + orders: [{ orderId: 100 }], } as FoodRequest); mockOrdersService.updateStatus.mockResolvedValue(); @@ -275,7 +275,7 @@ describe('RequestsController', () => { mockRequestsService.findOne.mockResolvedValue({ requestId, pantryId: 1, - order: { orderId: 101 }, + orders: [{ orderId: 101 }], } as FoodRequest); mockOrdersService.updateStatus.mockResolvedValue(); diff --git a/apps/backend/src/foodRequests/request.controller.ts b/apps/backend/src/foodRequests/request.controller.ts index 1f449491..0abf1922 100644 --- a/apps/backend/src/foodRequests/request.controller.ts +++ b/apps/backend/src/foodRequests/request.controller.ts @@ -16,7 +16,6 @@ import { AWSS3Service } from '../aws/aws-s3.service'; import { FilesInterceptor } from '@nestjs/platform-express'; import * as multer from 'multer'; import { OrdersService } from '../orders/order.service'; -import { Order } from '../orders/order.entity'; import { RequestSize } from './types'; import { OrderStatus } from '../orders/types'; @@ -158,9 +157,11 @@ export class RequestsController { ); const request = await this.requestsService.findOne(requestId); - await this.ordersService.updateStatus( - request.order.orderId, - OrderStatus.DELIVERED, + + await Promise.all( + request.orders.map((order) => + this.ordersService.updateStatus(order.orderId, OrderStatus.DELIVERED), + ), ); return this.requestsService.updateDeliveryDetails( diff --git a/apps/backend/src/foodRequests/request.entity.ts b/apps/backend/src/foodRequests/request.entity.ts index 06c2ce79..f745b804 100644 --- a/apps/backend/src/foodRequests/request.entity.ts +++ b/apps/backend/src/foodRequests/request.entity.ts @@ -46,6 +46,6 @@ export class FoodRequest { @Column({ name: 'photos', type: 'text', array: true, nullable: true }) photos: string[]; - @OneToMany(() => Order, (order) => order.request, { nullable: true }) - order: Order; + @OneToMany(() => Order, (order) => order.request) + orders: Order[]; } diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts index cc69a5a3..b05013d0 100644 --- a/apps/backend/src/foodRequests/request.service.spec.ts +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -21,7 +21,7 @@ const mockRequest: Partial = { dateReceived: null, feedback: null, photos: null, - order: null, + orders: null, }; describe('RequestsService', () => { @@ -166,7 +166,7 @@ describe('RequestsService', () => { dateReceived: null, feedback: null, photos: null, - order: null, + orders: null, }, { requestId: 3, @@ -178,7 +178,7 @@ describe('RequestsService', () => { dateReceived: null, feedback: null, photos: null, - order: null, + orders: null, }, ]; const pantryId = 1; @@ -213,7 +213,7 @@ describe('RequestsService', () => { const mockRequest2: Partial = { ...mockRequest, - order: mockOrder as Order, + orders: [mockOrder] as Order[], }; const requestId = 1; @@ -224,15 +224,15 @@ describe('RequestsService', () => { mockRequestsRepository.findOne.mockResolvedValueOnce( mockRequest2 as FoodRequest, ); + + const updatedOrder = { ...mockOrder, status: OrderStatus.DELIVERED }; + mockRequestsRepository.save.mockResolvedValueOnce({ ...mockRequest, dateReceived: deliveryDate, feedback, photos, - order: { - ...(mockOrder as Order), - status: OrderStatus.DELIVERED, - } as Order, + orders: [updatedOrder], } as FoodRequest); const result = await service.updateDeliveryDetails( @@ -247,7 +247,7 @@ describe('RequestsService', () => { dateReceived: deliveryDate, feedback, photos, - order: { ...mockOrder, status: 'delivered' }, + orders: [updatedOrder], }); expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ @@ -260,7 +260,7 @@ describe('RequestsService', () => { dateReceived: deliveryDate, feedback, photos, - order: { ...mockOrder, status: 'delivered' }, + orders: [updatedOrder], }); }); @@ -304,7 +304,7 @@ describe('RequestsService', () => { feedback, photos, ), - ).rejects.toThrow('No associated order found for this request'); + ).rejects.toThrow('No associated orders found for this request'); expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ where: { requestId }, @@ -327,7 +327,7 @@ describe('RequestsService', () => { }; const mockRequest2: Partial = { ...mockRequest, - order: mockOrder as Order, + orders: [mockOrder] as Order[], }; const requestId = 1; @@ -346,7 +346,9 @@ describe('RequestsService', () => { feedback, photos, ), - ).rejects.toThrow('No associated food manufacturer found for this order'); + ).rejects.toThrow( + 'No associated food manufacturer found for an associated order', + ); expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ where: { requestId }, diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts index 32e600ba..fe5d7f1c 100644 --- a/apps/backend/src/foodRequests/request.service.ts +++ b/apps/backend/src/foodRequests/request.service.ts @@ -87,22 +87,28 @@ export class RequestsService { throw new NotFoundException('Invalid request ID'); } - if (!request.order) { - throw new ConflictException('No associated order found for this request'); + if (!request.orders || request.orders.length == 0) { + throw new ConflictException( + 'No associated orders found for this request', + ); } - const order = request.order; + const orders = request.orders; - if (!order.shippedBy) { - throw new ConflictException( - 'No associated food manufacturer found for this order', - ); + for (const order of orders) { + if (!order.shippedBy) { + throw new ConflictException( + 'No associated food manufacturer found for an associated order', + ); + } } request.feedback = feedback; request.dateReceived = deliveryDate; request.photos = photos; - request.order.status = OrderStatus.DELIVERED; + request.orders.forEach((order) => { + order.status = OrderStatus.DELIVERED; + }); return await this.repo.save(request); } From 75a466191001b13be70ef670212078e1e8aa2876 Mon Sep 17 00:00:00 2001 From: Justin Wang Date: Wed, 14 Jan 2026 22:36:41 -0500 Subject: [PATCH 2/4] adding order details endpoint and placeholder order values for outdated request management frontend --- .../src/allocations/allocations.entity.ts | 5 +++ .../foodRequests/dtos/order-details.dto.ts | 15 ++++++++ .../src/foodRequests/request.controller.ts | 8 +++++ .../src/foodRequests/request.service.spec.ts | 20 +++++++---- .../src/foodRequests/request.service.ts | 35 +++++++++++++++++-- apps/backend/src/orders/order.entity.ts | 5 +++ apps/frontend/src/api/apiClient.ts | 5 +++ apps/frontend/src/containers/FormRequests.tsx | 16 ++++----- apps/frontend/src/types/types.ts | 15 +++++++- 9 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 apps/backend/src/foodRequests/dtos/order-details.dto.ts diff --git a/apps/backend/src/allocations/allocations.entity.ts b/apps/backend/src/allocations/allocations.entity.ts index a5a3c734..5a031df1 100644 --- a/apps/backend/src/allocations/allocations.entity.ts +++ b/apps/backend/src/allocations/allocations.entity.ts @@ -6,6 +6,7 @@ import { JoinColumn, } from 'typeorm'; import { DonationItem } from '../donationItems/donationItems.entity'; +import { Order } from '../orders/order.entity'; @Entity('allocations') export class Allocation { @@ -15,6 +16,10 @@ export class Allocation { @Column({ name: 'order_id', type: 'int' }) orderId: number; + @ManyToOne(() => Order, (order) => order.allocations) + @JoinColumn({ name: 'order_id' }) + order: Order; + @Column({ name: 'item_id', type: 'int', nullable: false }) itemId: number; diff --git a/apps/backend/src/foodRequests/dtos/order-details.dto.ts b/apps/backend/src/foodRequests/dtos/order-details.dto.ts new file mode 100644 index 00000000..2a0537df --- /dev/null +++ b/apps/backend/src/foodRequests/dtos/order-details.dto.ts @@ -0,0 +1,15 @@ +import { FoodType } from "../../donationItems/types"; +import { OrderStatus } from "../../orders/types"; + +export class OrderItemDetailsDto { + name: string; + quantity: number; + foodType: FoodType; +} + +export class OrderDetailsDto { + orderId: number; + status: OrderStatus; + foodManufacturerName: string; + items: OrderItemDetailsDto[]; +} diff --git a/apps/backend/src/foodRequests/request.controller.ts b/apps/backend/src/foodRequests/request.controller.ts index 0abf1922..0336b5a1 100644 --- a/apps/backend/src/foodRequests/request.controller.ts +++ b/apps/backend/src/foodRequests/request.controller.ts @@ -18,6 +18,7 @@ import * as multer from 'multer'; import { OrdersService } from '../orders/order.service'; import { RequestSize } from './types'; import { OrderStatus } from '../orders/types'; +import { OrderDetailsDto } from './dtos/order-details.dto'; @Controller('requests') // @UseInterceptors() @@ -42,6 +43,13 @@ export class RequestsController { return this.requestsService.find(pantryId); } + @Get('/get-all-order-details/:requestId') + async getAllOrderDetailsFromRequest( + @Param('requestId', ParseIntPipe) requestId: number, + ) : Promise { + return this.requestsService.getOrderDetails(requestId); + } + @Post('/create') @ApiBody({ description: 'Details for creating a food request', diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts index b05013d0..d691908d 100644 --- a/apps/backend/src/foodRequests/request.service.spec.ts +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -11,6 +11,8 @@ import { OrderStatus } from '../orders/types'; const mockRequestsRepository = mock>(); const mockPantryRepository = mock>(); +const mockOrdersRepository = mock>(); + const mockRequest: Partial = { requestId: 1, @@ -46,6 +48,10 @@ describe('RequestsService', () => { provide: getRepositoryToken(Pantry), useValue: mockPantryRepository, }, + { + provide: getRepositoryToken(Order), + useValue: mockOrdersRepository, + }, ], }).compile(); @@ -74,7 +80,7 @@ describe('RequestsService', () => { expect(result).toEqual(mockRequest); expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ where: { requestId }, - relations: ['order'], + relations: ['orders'], }); }); @@ -89,7 +95,7 @@ describe('RequestsService', () => { expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ where: { requestId }, - relations: ['order'], + relations: ['orders'], }); }); }); @@ -191,7 +197,7 @@ describe('RequestsService', () => { expect(result).toEqual(mockRequests.slice(0, 2)); expect(mockRequestsRepository.find).toHaveBeenCalledWith({ where: { pantryId }, - relations: ['order'], + relations: ['orders'], }); }); }); @@ -252,7 +258,7 @@ describe('RequestsService', () => { expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ where: { requestId }, - relations: ['order'], + relations: ['orders'], }); expect(mockRequestsRepository.save).toHaveBeenCalledWith({ @@ -283,7 +289,7 @@ describe('RequestsService', () => { expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ where: { requestId }, - relations: ['order'], + relations: ['orders'], }); }); @@ -308,7 +314,7 @@ describe('RequestsService', () => { expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ where: { requestId }, - relations: ['order'], + relations: ['orders'], }); }); @@ -352,7 +358,7 @@ describe('RequestsService', () => { expect(mockRequestsRepository.findOne).toHaveBeenCalledWith({ where: { requestId }, - relations: ['order'], + relations: ['orders'], }); }); }); diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts index fe5d7f1c..d00aca84 100644 --- a/apps/backend/src/foodRequests/request.service.ts +++ b/apps/backend/src/foodRequests/request.service.ts @@ -10,12 +10,15 @@ import { validateId } from '../utils/validation.utils'; import { RequestSize } from './types'; import { OrderStatus } from '../orders/types'; import { Pantry } from '../pantries/pantries.entity'; +import { Order } from '../orders/order.entity'; +import { OrderDetailsDto } from './dtos/order-details.dto'; @Injectable() export class RequestsService { constructor( @InjectRepository(FoodRequest) private repo: Repository, @InjectRepository(Pantry) private pantryRepo: Repository, + @InjectRepository(Order) private orderRepo: Repository, ) {} async findOne(requestId: number): Promise { @@ -23,7 +26,7 @@ export class RequestsService { const request = await this.repo.findOne({ where: { requestId }, - relations: ['order'], + relations: ['orders'], }); if (!request) { @@ -32,6 +35,32 @@ export class RequestsService { return request; } + async getOrderDetails( + requestId: number, + ): Promise { + const orders = await this.orderRepo.find({ + where: { requestId }, + relations: { + foodManufacturer: true, + allocations: { + item: true, + }, + }, + }); + + return orders.map((order) => ({ + orderId: order.orderId, + status: order.status, + foodManufacturerName: order.foodManufacturer.foodManufacturerName, + items: order.allocations.map((allocation) => ({ + name: allocation.item.itemName, + quantity: allocation.allocatedQuantity, + foodType: allocation.item.foodType, + })), + })); + } + + async create( pantryId: number, requestedSize: RequestSize, @@ -66,7 +95,7 @@ export class RequestsService { return await this.repo.find({ where: { pantryId }, - relations: ['order'], + relations: ['orders'], }); } @@ -80,7 +109,7 @@ export class RequestsService { const request = await this.repo.findOne({ where: { requestId }, - relations: ['order'], + relations: ['orders'], }); if (!request) { diff --git a/apps/backend/src/orders/order.entity.ts b/apps/backend/src/orders/order.entity.ts index 4c38457b..7c40fdb4 100644 --- a/apps/backend/src/orders/order.entity.ts +++ b/apps/backend/src/orders/order.entity.ts @@ -5,11 +5,13 @@ import { CreateDateColumn, ManyToOne, JoinColumn, + OneToMany, } from 'typeorm'; import { FoodRequest } from '../foodRequests/request.entity'; import { Pantry } from '../pantries/pantries.entity'; import { FoodManufacturer } from '../foodManufacturers/manufacturer.entity'; import { OrderStatus } from './types'; +import { Allocation } from '../allocations/allocations.entity'; @Entity('orders') export class Order { @@ -72,4 +74,7 @@ export class Order { nullable: true, }) deliveredAt: Date | null; + + @OneToMany(() => Allocation, (allocation) => allocation.order) + allocations: Allocation[]; } diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index f62efd8f..a677189a 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -11,6 +11,7 @@ import { Pantry, PantryApplicationDto, UserDto, + OrderDetails, } from 'types/types'; const defaultBaseUrl = @@ -193,6 +194,10 @@ export class ApiClient { return this.axiosInstance.get(`api/orders/${orderId}`) as Promise; } + public async getOrderDetailsListFromRequest(requestId: number): Promise { + return this.axiosInstance.get(`api/requests/get-all-order-details/${requestId}`) as Promise; + } + async getAllAllocationsByOrder(orderId: number): Promise { return this.axiosInstance .get(`api/orders/${orderId}/allocations`) diff --git a/apps/frontend/src/containers/FormRequests.tsx b/apps/frontend/src/containers/FormRequests.tsx index 50d20601..e747a55b 100644 --- a/apps/frontend/src/containers/FormRequests.tsx +++ b/apps/frontend/src/containers/FormRequests.tsx @@ -150,32 +150,32 @@ const FormRequests: React.FC = () => { - {request.order?.orderId ? ( + {request.orders?.[0]?.orderId ? ( ) : ( 'N/A' )} {formatDate(request.requestedAt)} - {request.order?.status ?? 'pending'} + {request.orders?.[0]?.status ?? 'pending'} - {request.order?.status === 'pending' + {request.orders?.[0]?.status === 'pending' ? 'N/A' - : request.order?.shippedBy ?? 'N/A'} + : request.orders?.[0]?.shippedBy ?? 'N/A'} {formatReceivedDate(request.dateReceived)} - {!request.order || request.order?.status === 'pending' ? ( + {!request.orders?.[0] || request.orders?.[0]?.status === 'pending' ? ( Awaiting Order Assignment - ) : request.order?.status === 'delivered' ? ( + ) : request.orders?.[0]?.status === 'delivered' ? ( Food Request is Already Delivered ) : (