Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6a7e711
logic for creating order itself
Juwang110 Mar 9, 2026
511b62f
functionality for creating allocations and updating donation item res…
Juwang110 Mar 10, 2026
efd05da
finish route functionality
Juwang110 Mar 10, 2026
51f80e3
all tests except create order service test
Juwang110 Mar 10, 2026
88dcbbd
finish test
Juwang110 Mar 10, 2026
9d46ed7
Merge branch 'main' into jw/ssf-153-volunteer-create-order-endpoint
Juwang110 Mar 11, 2026
64bc3c0
touch ups
Juwang110 Mar 11, 2026
3508e2a
comments
Juwang110 Mar 12, 2026
62f9920
Merge branch 'main' into jw/ssf-153-volunteer-create-order-endpoint
Juwang110 Mar 12, 2026
a9e177f
comment
Juwang110 Mar 12, 2026
10f7b38
comments
Juwang110 Mar 13, 2026
5a14191
comments
Juwang110 Mar 15, 2026
4352d38
fix import bug
Juwang110 Mar 15, 2026
e683f34
import
Juwang110 Mar 15, 2026
efc9544
Merge branch 'main' into jw/ssf-153-volunteer-create-order-endpoint
Juwang110 Mar 15, 2026
9c93121
import fix
Juwang110 Mar 15, 2026
cdd1796
comment
Juwang110 Mar 16, 2026
86e5e1d
remove controller endpoints
Juwang110 Mar 17, 2026
3011156
Merge branch 'main' into jw/ssf-153-volunteer-create-order-endpoint
Juwang110 Mar 18, 2026
ce640ab
comments
Juwang110 Mar 18, 2026
0289eec
Merge branch 'main' into jw/ssf-153-volunteer-create-order-endpoint
Juwang110 Mar 22, 2026
1682c1d
making transaction naming clear
Juwang110 Mar 22, 2026
4f35e13
Merge branch 'main' into jw/ssf-153-volunteer-create-order-endpoint
Juwang110 Mar 22, 2026
9836e98
comment
Juwang110 Mar 22, 2026
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
40 changes: 39 additions & 1 deletion apps/backend/src/allocations/allocations.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
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 { 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 +25,38 @@ export class AllocationsService {
},
});
}

async createMultiple(
orderId: number,
itemAllocations: Record<number, number>,
transactionManager?: EntityManager,
): Promise<Allocation[]> {
const repo = transactionManager
? transactionManager.getRepository(Allocation)
: this.repo;
const itemRepo = transactionManager
? transactionManager.getRepository(DonationItem)
: this.donationItemRepo;

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);
}
}
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 { 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 {}
32 changes: 32 additions & 0 deletions apps/backend/src/donations/donations.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,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
33 changes: 32 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,37 @@ export class DonationService {
return this.repo.save(donation);
}

async matchAll(
donationIds: number[],
transactionManager?: EntityManager,
): Promise<void> {
donationIds.forEach((id) => validateId(id, 'Donation'));

const repo = transactionManager
? transactionManager.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 {}
17 changes: 17 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,17 @@
import { IsInt, IsNotEmptyObject, IsObject, Min } from 'class-validator';

export class CreateOrderDto {
@IsInt()
@Min(1)
foodRequestId!: number;

@IsInt()
@Min(1)
manufacturerId!: number;

// This object is not fully validated, the validation is handled in the controller where the DTO is used.
// We would like this type to be Record<string, number> where the key is the donationItemId and the value is reserved quantity
@IsObject()
@IsNotEmptyObject()
itemAllocations!: Record<string, unknown>;
}
Loading
Loading