Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions apps/backend/src/allocations/allocations.controller.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion apps/backend/src/allocations/allocations.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
41 changes: 40 additions & 1 deletion apps/backend/src/allocations/allocations.service.ts
Original file line number Diff line number Diff line change
@@ -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<Allocation>,
@InjectRepository(DonationItem)
private donationItemRepo: Repository<DonationItem>,
) {}

async getAllAllocationsByOrder(
Expand All @@ -21,4 +26,38 @@ export class AllocationsService {
},
});
}

async createMultiple(
body: CreateMultipleAllocationsDto,
manager?: EntityManager,
): Promise<Allocation[]> {
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);
}
}
11 changes: 11 additions & 0 deletions apps/backend/src/allocations/dtos/create-allocations.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IsNotEmptyObject, IsNumber, IsObject, Min } from 'class-validator';

export class CreateMultipleAllocationsDto {
@IsNumber()
@Min(1)
orderId!: number;

@IsObject()
@IsNotEmptyObject()
itemAllocations!: Record<number, number>;
}
8 changes: 6 additions & 2 deletions apps/backend/src/donationItems/donationItems.module.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 {}
45 changes: 44 additions & 1 deletion apps/backend/src/donationItems/donationItems.service.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -28,6 +28,49 @@ export class DonationItemsService {
return this.repo.find({ where: { donation: { donationId } } });
}

async getByIds(donationItemIds: number[]): Promise<DonationItem[]> {
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<Set<number>> {
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,
Expand Down
8 changes: 6 additions & 2 deletions apps/backend/src/donations/donations.module.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 {}
34 changes: 33 additions & 1 deletion apps/backend/src/donations/donations.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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 () => {
Expand Down
31 changes: 30 additions & 1 deletion apps/backend/src/donations/donations.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -98,6 +98,35 @@ export class DonationService {
return this.repo.save(donation);
}

async matchAll(
donationIds: number[],
manager?: EntityManager,
): Promise<void> {
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<void> {
const donations = await this.getAll();
const today = new Date();
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/foodManufacturers/manufacturers.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ import { Donation } from '../donations/donations.entity';
],
controllers: [FoodManufacturersController],
providers: [FoodManufacturersService],
exports: [FoodManufacturersService],
})
export class ManufacturerModule {}
15 changes: 15 additions & 0 deletions apps/backend/src/orders/dtos/create-order.dto.ts
Original file line number Diff line number Diff line change
@@ -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<number, number>;
}
20 changes: 19 additions & 1 deletion apps/backend/src/orders/order.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Loading
Loading