diff --git a/apps/backend/src/config/migrations.ts b/apps/backend/src/config/migrations.ts index e4b74b60..f4cd5556 100644 --- a/apps/backend/src/config/migrations.ts +++ b/apps/backend/src/config/migrations.ts @@ -33,6 +33,7 @@ import { UpdateOrderEntity1769990652833 } from '../migrations/1769990652833-Upda import { DonationItemFoodTypeNotNull1771524930613 } from '../migrations/1771524930613-DonationItemFoodTypeNotNull'; import { MoveRequestFieldsToOrders1770571145350 } from '../migrations/1770571145350-MoveRequestFieldsToOrders'; import { RenameDonationMatchingStatus1771260403657 } from '../migrations/1771260403657-RenameDonationMatchingStatus'; +import { AddAssigneeToOrders1773009000618 } from '../migrations/1773009000618-AddAssigneeToOrders'; import { DropDonationTotalColumns1772241115031 } from '../migrations/1772241115031-DropDonationTotalColumns'; import { FixTrackingLinks1773041840374 } from '../migrations/1773041840374-FixTrackingLinks'; import { CleanupRequestsAndAllocations1771821377918 } from '../migrations/1771821377918-CleanupRequestsAndAllocations'; @@ -74,6 +75,7 @@ const schemaMigrations = [ DonationItemFoodTypeNotNull1771524930613, MoveRequestFieldsToOrders1770571145350, RenameDonationMatchingStatus1771260403657, + AddAssigneeToOrders1773009000618, DropDonationTotalColumns1772241115031, FixTrackingLinks1773041840374, CleanupRequestsAndAllocations1771821377918, diff --git a/apps/backend/src/migrations/1773009000618-AddAssigneeToOrders.ts b/apps/backend/src/migrations/1773009000618-AddAssigneeToOrders.ts new file mode 100644 index 00000000..3dd09838 --- /dev/null +++ b/apps/backend/src/migrations/1773009000618-AddAssigneeToOrders.ts @@ -0,0 +1,30 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAssigneeToOrders1773009000618 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE orders ADD COLUMN assignee_id INT`); + + await queryRunner.query(` + UPDATE orders o SET assignee_id = ( + SELECT va.volunteer_id FROM volunteer_assignments va + JOIN food_requests fr ON fr.pantry_id = va.pantry_id + WHERE fr.request_id = o.request_id + LIMIT 1 + ) + `); + + await queryRunner.query(` + ALTER TABLE orders + ALTER COLUMN assignee_id SET NOT NULL, + ADD CONSTRAINT fk_assignee_id FOREIGN KEY (assignee_id) REFERENCES users(user_id) ON DELETE RESTRICT + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE orders + DROP CONSTRAINT IF EXISTS fk_assignee_id, + DROP COLUMN assignee_id + `); + } +} diff --git a/apps/backend/src/orders/order.entity.ts b/apps/backend/src/orders/order.entity.ts index b8b3046a..2d3f123c 100644 --- a/apps/backend/src/orders/order.entity.ts +++ b/apps/backend/src/orders/order.entity.ts @@ -11,6 +11,7 @@ import { FoodRequest } from '../foodRequests/request.entity'; import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; import { OrderStatus } from './types'; import { Allocation } from '../allocations/allocations.entity'; +import { User } from '../users/users.entity'; @Entity('orders') export class Order { @@ -99,4 +100,11 @@ export class Order { }, }) shippingCost!: number | null; + + @ManyToOne(() => User, { nullable: false, onDelete: 'RESTRICT' }) + @JoinColumn({ name: 'assignee_id' }) + assignee!: User; + + @Column({ name: 'assignee_id', type: 'int' }) + assigneeId!: number; } diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index b0aa4588..aacf8f3b 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -578,7 +578,6 @@ describe('OrdersService', () => { it('should update order with delivery details and set status to delivered but request remains active', async () => { const orderRepo = testDataSource.getRepository(Order); const requestRepo = testDataSource.getRepository(FoodRequest); - // Get an existing shipped order const existingShippedOrder = await orderRepo.findOne({ where: { status: OrderStatus.SHIPPED }, @@ -594,6 +593,7 @@ describe('OrdersService', () => { const secondOrder = orderRepo.create({ requestId: existingShippedOrder.requestId, foodManufacturerId: existingShippedOrder.foodManufacturerId, + assigneeId: existingShippedOrder.assigneeId, status: OrderStatus.SHIPPED, shippedAt: new Date(), }); diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 74d620fe..c0103879 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -31,7 +31,7 @@ export class OrdersService { .createQueryBuilder('order') .leftJoinAndSelect('order.request', 'request') .leftJoinAndSelect('request.pantry', 'pantry') - .leftJoinAndSelect('pantry.volunteers', 'volunteers') + .leftJoinAndSelect('order.assignee', 'assignee') .select([ 'order.orderId', 'order.status', @@ -40,9 +40,9 @@ export class OrdersService { 'order.deliveredAt', 'request.pantryId', 'pantry.pantryName', - 'volunteers.id', - 'volunteers.firstName', - 'volunteers.lastName', + 'assignee.id', + 'assignee.firstName', + 'assignee.lastName', ]); if (filters?.status) { diff --git a/apps/backend/src/users/users.service.spec.ts b/apps/backend/src/users/users.service.spec.ts index ac38ff75..5e13fe7b 100644 --- a/apps/backend/src/users/users.service.spec.ts +++ b/apps/backend/src/users/users.service.spec.ts @@ -232,6 +232,11 @@ describe('UsersService', () => { describe('remove', () => { it('should remove a user by id', async () => { + await testDataSource.query( + `DELETE FROM allocations WHERE order_id IN (SELECT order_id FROM orders WHERE assignee_id = 6)`, + ); + await testDataSource.query(`DELETE FROM orders WHERE assignee_id = 6`); + const result = await service.remove(6); expect(result.email).toBe('james.t@volunteer.org'); diff --git a/apps/backend/src/volunteers/volunteers.service.spec.ts b/apps/backend/src/volunteers/volunteers.service.spec.ts index 34f889a9..a45d22b3 100644 --- a/apps/backend/src/volunteers/volunteers.service.spec.ts +++ b/apps/backend/src/volunteers/volunteers.service.spec.ts @@ -134,7 +134,8 @@ describe('VolunteersService', () => { describe('getVolunteersAndPantryAssignments', () => { it('returns an empty array when there are no volunteers', async () => { - // Delete all users with role 'volunteer' (CASCADE will handle related data) + await testDataSource.query(`DELETE FROM allocations`); + await testDataSource.query(`DELETE FROM orders`); await testDataSource.query( `DELETE FROM "users" WHERE role = 'volunteer'`, ); diff --git a/apps/frontend/src/containers/adminOrderManagement.tsx b/apps/frontend/src/containers/adminOrderManagement.tsx index cbec3241..bca491f3 100644 --- a/apps/frontend/src/containers/adminOrderManagement.tsx +++ b/apps/frontend/src/containers/adminOrderManagement.tsx @@ -110,25 +110,15 @@ const AdminOrderManagement: React.FC = () => { [OrderStatus.DELIVERED]: [], }; - // Use a status specific counter for assignee color assignment - const counters: Record = { - [OrderStatus.SHIPPED]: 0, - [OrderStatus.PENDING]: 0, - [OrderStatus.DELIVERED]: 0, - }; - for (const order of data) { const status = order.status; - const orderWithColor: OrderWithColor = { ...order }; - if ( - order.request.pantry.volunteers && - order.request.pantry.volunteers.length > 0 - ) { + + if (order.assignee) { orderWithColor.assigneeColor = - ASSIGNEE_COLORS[counters[status] % ASSIGNEE_COLORS.length]; - counters[status]++; + ASSIGNEE_COLORS[order.assignee.id % ASSIGNEE_COLORS.length]; } + grouped[status].push(orderWithColor); } @@ -614,7 +604,6 @@ const OrderStatusSection: React.FC = ({ {orders.map((order, index) => { const pantry = order.request.pantry; - const volunteers = pantry.volunteers || []; return ( = ({ alignItems="center" justifyContent="center" > - {volunteers && volunteers.length > 0 ? ( - - {/* TODO: Change logic later to only get one volunteer */} - {getInitials( - volunteers[0].firstName, - volunteers[0].lastName, - )} - - ) : ( - No Assignees - )} + + {order.assignee.firstName.charAt(0).toUpperCase()} + {order.assignee.lastName.charAt(0).toUpperCase()} + = ({ - {/* TODO: IMPLEMENT WHAT GOES HERE */} - + bg="#FAFAFA" + > ); })} diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index 3f28c59d..072cfc0b 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -356,6 +356,11 @@ export interface OrderSummary { }[]; }; }; + assignee: { + id: number; + firstName: string; + lastName: string; + }; } export enum ApplicationStatus {