diff --git a/apps/backend/src/allocations/allocations.controller.ts b/apps/backend/src/allocations/allocations.controller.ts index d8d2324d..06cdd4fc 100644 --- a/apps/backend/src/allocations/allocations.controller.ts +++ b/apps/backend/src/allocations/allocations.controller.ts @@ -1,6 +1,5 @@ -import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { Controller } from '@nestjs/common'; import { AllocationsService } from './allocations.service'; -import { Allocation } from './allocations.entity'; @Controller('allocations') export class AllocationsController { diff --git a/apps/backend/src/allocations/allocations.module.ts b/apps/backend/src/allocations/allocations.module.ts index 3284e1af..7d62e964 100644 --- a/apps/backend/src/allocations/allocations.module.ts +++ b/apps/backend/src/allocations/allocations.module.ts @@ -4,11 +4,14 @@ import { Allocation } from './allocations.entity'; import { AllocationsController } from './allocations.controller'; import { AllocationsService } from './allocations.service'; import { AuthModule } from '../auth/auth.module'; +import { DonationItemsModule } from '../donationItems/donationItems.module'; +import { DonationItem } from '../donationItems/donationItems.entity'; @Module({ imports: [ - TypeOrmModule.forFeature([Allocation]), + TypeOrmModule.forFeature([Allocation, DonationItem]), forwardRef(() => AuthModule), + DonationItemsModule, ], controllers: [AllocationsController], providers: [AllocationsService], diff --git a/apps/backend/src/allocations/allocations.service.ts b/apps/backend/src/allocations/allocations.service.ts index 23634f67..b4235dca 100644 --- a/apps/backend/src/allocations/allocations.service.ts +++ b/apps/backend/src/allocations/allocations.service.ts @@ -1,12 +1,17 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { EntityManager, Repository } from 'typeorm'; import { Allocation } from '../allocations/allocations.entity'; +import { CreateMultipleAllocationsDto } from './dtos/create-allocations.dto'; +import { validateId } from '../utils/validation.utils'; +import { DonationItem } from '../donationItems/donationItems.entity'; @Injectable() export class AllocationsService { constructor( @InjectRepository(Allocation) private repo: Repository, + @InjectRepository(DonationItem) + private donationItemRepo: Repository, ) {} async getAllAllocationsByOrder( @@ -21,4 +26,38 @@ export class AllocationsService { }, }); } + + async createMultiple( + body: CreateMultipleAllocationsDto, + manager?: EntityManager, + ): Promise { + const repo = manager ? manager.getRepository(Allocation) : this.repo; + const itemRepo = manager + ? manager.getRepository(DonationItem) + : this.donationItemRepo; + + const orderId = body.orderId; + const itemAllocations = body.itemAllocations; + + validateId(orderId, 'Order'); + + const allocations: Allocation[] = []; + + for (const [itemIdStr, quantity] of Object.entries(itemAllocations)) { + const itemId = Number(itemIdStr); + validateId(itemId, 'Donation Item'); + + allocations.push( + repo.create({ + orderId, + itemId, + allocatedQuantity: quantity, + }), + ); + + await itemRepo.increment({ itemId }, 'reservedQuantity', quantity); + } + + return repo.save(allocations); + } } diff --git a/apps/backend/src/allocations/dtos/create-allocations.dto.ts b/apps/backend/src/allocations/dtos/create-allocations.dto.ts new file mode 100644 index 00000000..f11aed64 --- /dev/null +++ b/apps/backend/src/allocations/dtos/create-allocations.dto.ts @@ -0,0 +1,11 @@ +import { IsNotEmptyObject, IsNumber, IsObject, Min } from 'class-validator'; + +export class CreateMultipleAllocationsDto { + @IsNumber() + @Min(1) + orderId!: number; + + @IsObject() + @IsNotEmptyObject() + itemAllocations!: Record; +} diff --git a/apps/backend/src/donationItems/donationItems.module.ts b/apps/backend/src/donationItems/donationItems.module.ts index ef377d2b..657cf653 100644 --- a/apps/backend/src/donationItems/donationItems.module.ts +++ b/apps/backend/src/donationItems/donationItems.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { DonationItemsService } from './donationItems.service'; import { DonationItem } from './donationItems.entity'; @@ -7,8 +7,12 @@ import { AuthModule } from '../auth/auth.module'; import { Donation } from '../donations/donations.entity'; @Module({ - imports: [TypeOrmModule.forFeature([DonationItem, Donation]), AuthModule], + imports: [ + TypeOrmModule.forFeature([DonationItem, Donation]), + forwardRef(() => AuthModule), + ], controllers: [DonationItemsController], providers: [DonationItemsService], + exports: [DonationItemsService], }) export class DonationItemsModule {} diff --git a/apps/backend/src/donationItems/donationItems.service.ts b/apps/backend/src/donationItems/donationItems.service.ts index 2397f545..4e36a8ff 100644 --- a/apps/backend/src/donationItems/donationItems.service.ts +++ b/apps/backend/src/donationItems/donationItems.service.ts @@ -1,6 +1,6 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { EntityManager, In, Repository } from 'typeorm'; import { DonationItem } from './donationItems.entity'; import { validateId } from '../utils/validation.utils'; import { FoodType } from './types'; @@ -28,6 +28,49 @@ export class DonationItemsService { return this.repo.find({ where: { donation: { donationId } } }); } + async getByIds(donationItemIds: number[]): Promise { + donationItemIds.forEach((id) => validateId(id, 'Donation Item')); + + const items = await this.repo.find({ + where: { itemId: In(donationItemIds) }, + }); + + const foundIds = new Set(items.map((item) => item.itemId)); + + const missingIds = donationItemIds.filter((id) => !foundIds.has(id)); + + if (missingIds.length > 0) { + throw new NotFoundException( + `Donation items not found for ID(s): ${missingIds.join(', ')}`, + ); + } + + return items; + } + + async getAssociatedDonationIds( + donationItemIds: number[], + ): Promise> { + donationItemIds.forEach((id) => validateId(id, 'Donation Item')); + + const items = await this.repo.find({ + where: { itemId: In(donationItemIds) }, + select: ['itemId', 'donationId'], + }); + + const foundIds = new Set(items.map((i) => i.itemId)); + + const missingIds = donationItemIds.filter((id) => !foundIds.has(id)); + + if (missingIds.length > 0) { + throw new NotFoundException( + `Donation items not found for ID(s): ${missingIds.join(', ')}`, + ); + } + + return new Set(items.map((i) => i.donationId)); + } + async create( donationId: number, itemName: string, diff --git a/apps/backend/src/donations/donations.module.ts b/apps/backend/src/donations/donations.module.ts index 0b813de8..71ffbc7d 100644 --- a/apps/backend/src/donations/donations.module.ts +++ b/apps/backend/src/donations/donations.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Donation } from './donations.entity'; import { DonationService } from './donations.service'; @@ -8,8 +8,12 @@ import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; import { DonationsSchedulerService } from './donations.scheduler'; @Module({ - imports: [TypeOrmModule.forFeature([Donation, FoodManufacturer]), AuthModule], + imports: [ + TypeOrmModule.forFeature([Donation, FoodManufacturer]), + forwardRef(() => AuthModule), + ], controllers: [DonationsController], providers: [DonationService, DonationsSchedulerService], + exports: [DonationService], }) export class DonationModule {} diff --git a/apps/backend/src/donations/donations.service.spec.ts b/apps/backend/src/donations/donations.service.spec.ts index 84da409c..a4909d71 100644 --- a/apps/backend/src/donations/donations.service.spec.ts +++ b/apps/backend/src/donations/donations.service.spec.ts @@ -3,7 +3,7 @@ import { getRepositoryToken } from '@nestjs/typeorm'; import { Donation } from './donations.entity'; import { DonationService } from './donations.service'; import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; -import { RecurrenceEnum, DayOfWeek } from './types'; +import { RecurrenceEnum, DayOfWeek, DonationStatus } from './types'; import { RepeatOnDaysDto } from './dtos/create-donation.dto'; import { testDataSource } from '../config/typeormTestDataSource'; @@ -136,6 +136,38 @@ describe('DonationService', () => { }); }); + describe('matchAll', () => { + it('updates all given donations to have status MATCHED', async () => { + const donationId1 = 1; + const donationId2 = 2; + const donationIds = [donationId1, donationId2]; + + const donation1 = await service.findOne(donationId1); + const donation2 = await service.findOne(donationId2); + expect(donation1.status).toEqual(DonationStatus.AVAILABLE); + expect(donation2.status).toEqual(DonationStatus.MATCHED); + + await service.matchAll(donationIds); + + const updatedDonation1 = await service.findOne(donationId1); + const updatedDonation2 = await service.findOne(donationId2); + + expect(updatedDonation1.status).toEqual(DonationStatus.MATCHED); + expect(updatedDonation2.status).toEqual(DonationStatus.MATCHED); + }); + + it('throws an error if one or more donationIds do not exist', async () => { + const existingDonationId = 1; + const nonExistingDonationId = 999; + + const donationIds = [existingDonationId, nonExistingDonationId]; + + await expect(service.matchAll(donationIds)).rejects.toThrow( + `Donations not found for ID(s): ${nonExistingDonationId}`, + ); + }); + }); + describe('handleRecurringDonations', () => { describe('no-op cases', () => { it('skips donation with no nextDonationDates', async () => { diff --git a/apps/backend/src/donations/donations.service.ts b/apps/backend/src/donations/donations.service.ts index 3e58fcc9..e2b539d4 100644 --- a/apps/backend/src/donations/donations.service.ts +++ b/apps/backend/src/donations/donations.service.ts @@ -5,7 +5,7 @@ import { NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { EntityManager, In, Repository } from 'typeorm'; import { Donation } from './donations.entity'; import { validateId } from '../utils/validation.utils'; import { DayOfWeek, DonationStatus, RecurrenceEnum } from './types'; @@ -98,6 +98,35 @@ export class DonationService { return this.repo.save(donation); } + async matchAll( + donationIds: number[], + manager?: EntityManager, + ): Promise { + donationIds.forEach((id) => validateId(id, 'Donation')); + + const repo = manager ? manager.getRepository(Donation) : this.repo; + + const donations = await repo.find({ + where: { donationId: In(donationIds) }, + select: ['donationId'], + }); + + const foundIds = donations.map((d) => d.donationId); + + const missingIds = donationIds.filter((id) => !foundIds.includes(id)); + + if (missingIds.length > 0) { + throw new NotFoundException( + `Donations not found for ID(s): ${missingIds.join(', ')}`, + ); + } + + await repo.update( + { donationId: In(donationIds) }, + { status: DonationStatus.MATCHED }, + ); + } + async handleRecurringDonations(): Promise { const donations = await this.getAll(); const today = new Date(); diff --git a/apps/backend/src/foodManufacturers/manufacturers.module.ts b/apps/backend/src/foodManufacturers/manufacturers.module.ts index e80b08f5..83df4ce7 100644 --- a/apps/backend/src/foodManufacturers/manufacturers.module.ts +++ b/apps/backend/src/foodManufacturers/manufacturers.module.ts @@ -13,5 +13,6 @@ import { Donation } from '../donations/donations.entity'; ], controllers: [FoodManufacturersController], providers: [FoodManufacturersService], + exports: [FoodManufacturersService], }) export class ManufacturerModule {} diff --git a/apps/backend/src/orders/dtos/create-order.dto.ts b/apps/backend/src/orders/dtos/create-order.dto.ts new file mode 100644 index 00000000..b822f463 --- /dev/null +++ b/apps/backend/src/orders/dtos/create-order.dto.ts @@ -0,0 +1,15 @@ +import { IsNotEmptyObject, IsNumber, IsObject, Min } from 'class-validator'; + +export class CreateOrderDto { + @IsNumber() + @Min(1) + foodRequestId!: number; + + @Min(1) + @IsNumber() + manufacturerId!: number; + + @IsObject() + @IsNotEmptyObject() + itemAllocations!: Record; +} diff --git a/apps/backend/src/orders/order.module.ts b/apps/backend/src/orders/order.module.ts index c312b0d1..71003cc7 100644 --- a/apps/backend/src/orders/order.module.ts +++ b/apps/backend/src/orders/order.module.ts @@ -10,15 +10,33 @@ import { FoodRequest } from '../foodRequests/request.entity'; import { AWSS3Module } from '../aws/aws-s3.module'; import { MulterModule } from '@nestjs/platform-express'; import { RequestsModule } from '../foodRequests/request.module'; +import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; +import { DonationItem } from '../donationItems/donationItems.entity'; +import { ManufacturerModule } from '../foodManufacturers/manufacturers.module'; +import { DonationItemsModule } from '../donationItems/donationItems.module'; +import { Allocation } from '../allocations/allocations.entity'; +import { DonationModule } from '../donations/donations.module'; +import { Donation } from '../donations/donations.entity'; @Module({ imports: [ - TypeOrmModule.forFeature([Order, Pantry, FoodRequest]), + TypeOrmModule.forFeature([ + Order, + Pantry, + FoodRequest, + FoodManufacturer, + DonationItem, + Allocation, + Donation, + ]), AllocationModule, forwardRef(() => AuthModule), AWSS3Module, MulterModule.register({ dest: './uploads' }), forwardRef(() => RequestsModule), + ManufacturerModule, + DonationItemsModule, + DonationModule, ], controllers: [OrdersController], providers: [OrdersService], diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index d0e3da93..53320940 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -13,8 +13,20 @@ import { FoodRequest } from '../foodRequests/request.entity'; import 'multer'; import { FoodRequestStatus } from '../foodRequests/types'; import { RequestsService } from '../foodRequests/request.service'; +import { FoodManufacturersService } from '../foodManufacturers/manufacturers.service'; +import { DonationItemsService } from '../donationItems/donationItems.service'; +import { AllocationsService } from '../allocations/allocations.service'; import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; +import { UsersService } from '../users/users.service'; import { DonationItem } from '../donationItems/donationItems.entity'; +import { Donation } from '../donations/donations.entity'; +import { Allocation } from '../allocations/allocations.entity'; +import { User } from '../users/users.entity'; +import { AuthService } from '../auth/auth.service'; +import { DonationService } from '../donations/donations.service'; +import { CreateOrderDto } from './dtos/create-order.dto'; +import { DonationStatus } from '../donations/types'; +import { DataSource } from 'typeorm'; // Set 1 minute timeout for async DB operations jest.setTimeout(60000); @@ -36,10 +48,23 @@ describe('OrdersService', () => { providers: [ OrdersService, RequestsService, + FoodManufacturersService, + DonationItemsService, + AllocationsService, + UsersService, + DonationService, + { + provide: DataSource, + useValue: testDataSource, + }, { provide: getRepositoryToken(Order), useValue: testDataSource.getRepository(Order), }, + { + provide: getRepositoryToken(FoodManufacturer), + useValue: testDataSource.getRepository(FoodManufacturer), + }, { provide: getRepositoryToken(Pantry), useValue: testDataSource.getRepository(Pantry), @@ -48,14 +73,26 @@ describe('OrdersService', () => { provide: getRepositoryToken(FoodRequest), useValue: testDataSource.getRepository(FoodRequest), }, - { - provide: getRepositoryToken(FoodManufacturer), - useValue: testDataSource.getRepository(FoodManufacturer), - }, { provide: getRepositoryToken(DonationItem), useValue: testDataSource.getRepository(DonationItem), }, + { + provide: getRepositoryToken(Donation), + useValue: testDataSource.getRepository(Donation), + }, + { + provide: getRepositoryToken(Allocation), + useValue: testDataSource.getRepository(Allocation), + }, + { + provide: getRepositoryToken(User), + useValue: testDataSource.getRepository(User), + }, + { + provide: AuthService, + useValue: {}, + }, ], }).compile(); @@ -677,4 +714,117 @@ describe('OrdersService', () => { ); }); }); + + describe('createOrder', () => { + let validCreateOrderDto: CreateOrderDto; + + beforeEach(() => { + validCreateOrderDto = { + foodRequestId: 1, + manufacturerId: 1, + itemAllocations: { + 1: 10, + 2: 3, + }, + }; + }); + + it('should create a new order successfully', async () => { + const allocationRepo = testDataSource.getRepository(Allocation); + const donationItemRepo = testDataSource.getRepository(DonationItem); + const donationRepo = testDataSource.getRepository(Donation); + + // Initial donation items + const donationItem1 = await donationItemRepo.findOne({ + where: { itemId: 1 }, + }); + const donationItem2 = await donationItemRepo.findOne({ + where: { itemId: 2 }, + }); + + if (!donationItem1 || !donationItem2) + throw new Error('Missing dummy donation items'); + + const createdOrder = await service.create(validCreateOrderDto); + + expect(createdOrder).toBeDefined(); + expect(createdOrder.orderId).toBeDefined(); + expect(createdOrder.status).toEqual(OrderStatus.PENDING); + expect(createdOrder.foodManufacturerId).toEqual( + validCreateOrderDto.manufacturerId, + ); + expect(createdOrder.requestId).toEqual(validCreateOrderDto.foodRequestId); + + const allocations = await allocationRepo.find({ + where: { orderId: createdOrder.orderId }, + }); + expect(allocations.length).toBe( + Object.keys(validCreateOrderDto.itemAllocations).length, + ); + expect(allocations.map((a) => a.itemId)).toEqual( + expect.arrayContaining([1, 2]), + ); + + const updatedDonation1 = await donationItemRepo.findOne({ + where: { itemId: 1 }, + }); + const updatedDonation2 = await donationItemRepo.findOne({ + where: { itemId: 2 }, + }); + + expect(updatedDonation1!.reservedQuantity).toBe( + donationItem1.reservedQuantity + 10, + ); + expect(updatedDonation2!.reservedQuantity).toBe( + donationItem2.reservedQuantity + 3, + ); + + const matchedDonation = await donationRepo.findOne({ + where: { donationId: 1 }, + }); + expect(matchedDonation?.status).toBe(DonationStatus.MATCHED); + }); + + it('should throw BadRequestException if request is not active', async () => { + const requestRepo = testDataSource.getRepository(FoodRequest); + + const request = await requestRepo.findOne({ where: { requestId: 2 } }); + + if (!request) throw new Error('Missing dummy request'); + + request.status = FoodRequestStatus.CLOSED; + await requestRepo.save(request); + + validCreateOrderDto.foodRequestId = 2; + await expect(service.create(validCreateOrderDto)).rejects.toThrow( + BadRequestException, + ); + await expect(service.create(validCreateOrderDto)).rejects.toThrow( + `Request ${validCreateOrderDto.foodRequestId} is not active`, + ); + }); + + it('should throw BadRequestException if allocated quantity exceeds remaining', async () => { + const donationItemId = 2; + + validCreateOrderDto.itemAllocations = { [donationItemId]: 500 }; + await expect(service.create(validCreateOrderDto)).rejects.toThrow( + BadRequestException, + ); + await expect(service.create(validCreateOrderDto)).rejects.toThrow( + `Donation item ${donationItemId} quantity to allocate exceeds remaining quantity`, + ); + }); + + it('should throw Error if donation is not associated with manufacturer', async () => { + const donationItemId = 7; + validCreateOrderDto.itemAllocations = { [donationItemId]: 2 }; + await expect(service.create(validCreateOrderDto)).rejects.toThrow( + BadRequestException, + ); + await expect(service.create(validCreateOrderDto)).rejects.toThrow( + `The following donation items are not associated with the current food manufacturer: Donation item ID ${donationItemId} with Donation ID 3`, + ); + }); + }); }); diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 74d620fe..09716a6c 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -3,8 +3,8 @@ import { Injectable, NotFoundException, } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, In } from 'typeorm'; +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { Repository, In, DataSource } from 'typeorm'; import { Order } from './order.entity'; import { Pantry } from '../pantries/pantries.entity'; import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; @@ -15,6 +15,12 @@ import { OrderDetailsDto } from './dtos/order-details.dto'; import { FoodRequestSummaryDto } from '../foodRequests/dtos/food-request-summary.dto'; import { ConfirmDeliveryDto } from './dtos/confirm-delivery.dto'; import { RequestsService } from '../foodRequests/request.service'; +import { CreateOrderDto } from './dtos/create-order.dto'; +import { FoodRequestStatus } from '../foodRequests/types'; +import { FoodManufacturersService } from '../foodManufacturers/manufacturers.service'; +import { DonationItemsService } from '../donationItems/donationItems.service'; +import { AllocationsService } from '../allocations/allocations.service'; +import { DonationService } from '../donations/donations.service'; @Injectable() export class OrdersService { @@ -22,6 +28,11 @@ export class OrdersService { @InjectRepository(Order) private repo: Repository, @InjectRepository(Pantry) private pantryRepo: Repository, private requestsService: RequestsService, + private manufacturerService: FoodManufacturersService, + private donationItemsService: DonationItemsService, + private allocationsService: AllocationsService, + private donationService: DonationService, + @InjectDataSource() private dataSource: DataSource, ) {} // TODO: when order is created, set FM @@ -70,6 +81,104 @@ export class OrdersService { }); } + /* + This create method follows these high level steps: + 1. Validate the request status is active before allowing order creation. + 2. Ensure all donation items belong to the specified manufacturer. + 3. Validate allocated quantities do not exceed the remaining quantity (quantity - reserved_quantity). + 4. Create the order with status pending. + 5. Associate the order with the provided request and manufacturer. + 6. Create allocation records for each donation item included in the order. + 7. Update the reserved quantity for each allocated donation item. + 8. Identify all unique donations associated with the allocated donation items and set their status to matched. + */ + async create(orderData: CreateOrderDto): Promise { + return this.dataSource.transaction(async (manager) => { + const requestId = orderData.foodRequestId; + const manufacturerId = orderData.manufacturerId; + const itemAllocations = orderData.itemAllocations; + + validateId(manufacturerId, 'Food Manufacturer'); + validateId(requestId, 'Request'); + + const request = await this.requestsService.findOne(requestId); + + if (request.status !== FoodRequestStatus.ACTIVE) { + throw new BadRequestException(`Request ${requestId} is not active`); + } + + const fmDonations = await this.manufacturerService.getFMDonations( + manufacturerId, + ); + const fmDonationIdSet = new Set(fmDonations.map((d) => d.donationId)); + + const donationItemIds = Object.keys(itemAllocations).map(Number); + const donationItems = await this.donationItemsService.getByIds( + donationItemIds, + ); + + const invalidItems = donationItems.filter( + (item) => !fmDonationIdSet.has(item.donationId), + ); + + if (invalidItems.length > 0) { + const messages = invalidItems.map( + (item) => + `Donation item ID ${item.itemId} with Donation ID ${item.donationId}`, + ); + throw new BadRequestException( + `The following donation items are not associated with the current food manufacturer: ${messages.join( + ', ', + )}`, + ); + } + + for (const donationItem of donationItems) { + const id = donationItem.itemId; + const quantityToAllocate = itemAllocations[id]; + + if (quantityToAllocate === undefined) continue; + + if ( + quantityToAllocate > + donationItem.quantity - donationItem.reservedQuantity + ) { + throw new BadRequestException( + `Donation item ${id} quantity to allocate exceeds remaining quantity`, + ); + } + } + + const order = manager.create(Order, { + requestId: requestId, + foodManufacturerId: manufacturerId, + status: OrderStatus.PENDING, + }); + + const savedOrder = await manager.save(order); + + await this.allocationsService.createMultiple( + { + orderId: savedOrder.orderId, + itemAllocations: itemAllocations, + }, + manager, + ); + + const associatedDonationIdsSet = + await this.donationItemsService.getAssociatedDonationIds( + donationItemIds, + ); + + await this.donationService.matchAll( + Array.from(associatedDonationIdsSet), + manager, + ); + + return savedOrder; + }); + } + async findOne(orderId: number): Promise { validateId(orderId, 'Order'); diff --git a/apps/backend/src/pantries/pantries.service.spec.ts b/apps/backend/src/pantries/pantries.service.spec.ts index e6ed91f0..dc896aad 100644 --- a/apps/backend/src/pantries/pantries.service.spec.ts +++ b/apps/backend/src/pantries/pantries.service.spec.ts @@ -27,6 +27,9 @@ import { Donation } from '../donations/donations.entity'; import { FoodManufacturersService } from '../foodManufacturers/manufacturers.service'; import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; import { User } from '../users/users.entity'; +import { AllocationsService } from '../allocations/allocations.service'; +import { Allocation } from '../allocations/allocations.entity'; +import { DataSource } from 'typeorm'; jest.setTimeout(60000); @@ -77,6 +80,7 @@ describe('PantriesService', () => { DonationItemsService, DonationService, FoodManufacturersService, + AllocationsService, { provide: AuthService, useValue: { @@ -111,6 +115,14 @@ describe('PantriesService', () => { provide: getRepositoryToken(FoodManufacturer), useValue: testDataSource.getRepository(FoodManufacturer), }, + { + provide: getRepositoryToken(Allocation), + useValue: testDataSource.getRepository(Allocation), + }, + { + provide: DataSource, + useValue: testDataSource, + }, ], }).compile(); diff --git a/apps/backend/src/volunteers/volunteers.service.spec.ts b/apps/backend/src/volunteers/volunteers.service.spec.ts index 35e5a92c..2df2802e 100644 --- a/apps/backend/src/volunteers/volunteers.service.spec.ts +++ b/apps/backend/src/volunteers/volunteers.service.spec.ts @@ -18,6 +18,9 @@ import { DonationItem } from '../donationItems/donationItems.entity'; import { DonationItemsService } from '../donationItems/donationItems.service'; import { DonationService } from '../donations/donations.service'; import { Donation } from '../donations/donations.entity'; +import { Allocation } from '../allocations/allocations.entity'; +import { AllocationsService } from '../allocations/allocations.service'; +import { DataSource } from 'typeorm'; jest.setTimeout(60000); @@ -40,6 +43,7 @@ describe('VolunteersService', () => { FoodManufacturersService, DonationItemsService, DonationService, + AllocationsService, { provide: AuthService, useValue: { @@ -74,6 +78,14 @@ describe('VolunteersService', () => { provide: getRepositoryToken(Donation), useValue: testDataSource.getRepository(Donation), }, + { + provide: getRepositoryToken(Allocation), + useValue: testDataSource.getRepository(Allocation), + }, + { + provide: DataSource, + useValue: testDataSource, + }, ], }).compile();