diff --git a/apps/backend/src/config/typeorm.ts b/apps/backend/src/config/typeorm.ts index 82384673..9f4faf76 100644 --- a/apps/backend/src/config/typeorm.ts +++ b/apps/backend/src/config/typeorm.ts @@ -27,6 +27,7 @@ import { RemoveMultipleVolunteerTypes1764811878152 } from '../migrations/1764811 import { RemoveUnusedStatuses1764816885341 } from '../migrations/1764816885341-RemoveUnusedStatuses'; import { UpdatePantryFields1763762628431 } from '../migrations/1763762628431-UpdatePantryFields'; import { PopulateDummyData1768501812134 } from '../migrations/1768501812134-populateDummyData'; +import { UpdateOrderEntity1769990652833 } from '../migrations/1769990652833-UpdateOrderEntity'; const config = { type: 'postgres', @@ -67,6 +68,7 @@ const config = { RemoveMultipleVolunteerTypes1764811878152, RemoveUnusedStatuses1764816885341, PopulateDummyData1768501812134, + UpdateOrderEntity1769990652833, ], }; diff --git a/apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts b/apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts new file mode 100644 index 00000000..780483df --- /dev/null +++ b/apps/backend/src/migrations/1769990652833-UpdateOrderEntity.ts @@ -0,0 +1,24 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateOrderEntity1769990652833 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE orders + ADD COLUMN IF NOT EXISTS tracking_link VARCHAR(255), + ADD COLUMN IF NOT EXISTS shipping_cost NUMERIC(10,2); + + UPDATE orders + SET tracking_link = 'www.samplelink/samplelink', + shipping_cost = 20.00 + WHERE status = 'delivered' OR status = 'shipped' AND shipped_at IS NOT NULL; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE orders + DROP COLUMN IF EXISTS tracking_link, + DROP COLUMN IF EXISTS shipping_cost; + `); + } +} diff --git a/apps/backend/src/orders/dtos/tracking-cost.dto.ts b/apps/backend/src/orders/dtos/tracking-cost.dto.ts new file mode 100644 index 00000000..58e817b4 --- /dev/null +++ b/apps/backend/src/orders/dtos/tracking-cost.dto.ts @@ -0,0 +1,10 @@ +import { IsUrl, IsNumber, Min } from "class-validator"; + +export class TrackingCostDto { + @IsUrl({}, { message: 'Tracking link must be a valid URL' }) + trackingLink: string; + + @IsNumber({ maxDecimalPlaces: 2, }, { message: 'Shipping cost must have at most 2 decimal places' }) + @Min(0, { message: 'Shipping cost cannot be negative' }) + shippingCost: number; +} \ No newline at end of file diff --git a/apps/backend/src/orders/order.controller.spec.ts b/apps/backend/src/orders/order.controller.spec.ts index 4c06f83a..400e322b 100644 --- a/apps/backend/src/orders/order.controller.spec.ts +++ b/apps/backend/src/orders/order.controller.spec.ts @@ -6,6 +6,7 @@ import { Order } from './order.entity'; import { Allocation } from '../allocations/allocations.entity'; import { mock } from 'jest-mock-extended'; import { OrderStatus } from './types'; +import { TrackingCostDto } from './dtos/tracking-cost.dto'; const mockOrdersService = mock(); const mockAllocationsService = mock(); @@ -73,4 +74,20 @@ describe('OrdersController', () => { ).toHaveBeenCalledWith(orderId); }); }); + + describe('updateTrackingAndCost', () => { + it('should call ordersService.updateTrackingAndCost with correct parameters', async () => { + const orderId = 1; + const trackingLink = 'www.samplelink/samplelink'; + const shippingCost = 15.99; + const dto: TrackingCostDto = { trackingLink, shippingCost }; + + await controller.updateTrackingAndCost(orderId, dto); + + expect(mockOrdersService.updateTrackingAndCost).toHaveBeenCalledWith( + orderId, + dto, + ); + }); + }); }); diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index 870dc1ef..994617b5 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -7,6 +7,7 @@ import { Body, Query, BadRequestException, + ValidationPipe, } from '@nestjs/common'; import { OrdersService } from './order.service'; import { Order } from './order.entity'; @@ -15,6 +16,7 @@ import { FoodManufacturer } from '../foodManufacturers/manufacturer.entity'; import { FoodRequest } from '../foodRequests/request.entity'; import { AllocationsService } from '../allocations/allocations.service'; import { OrderStatus } from './types'; +import { TrackingCostDto } from './dtos/tracking-cost.dto'; @Controller('orders') export class OrdersController { @@ -99,4 +101,13 @@ export class OrdersController { } return this.ordersService.updateStatus(orderId, newStatus as OrderStatus); } + + @Patch('/:orderId/update-tracking-and-cost') + async updateTrackingAndCost( + @Param('orderId', ParseIntPipe) orderId: number, + @Body(new ValidationPipe()) + dto: TrackingCostDto, + ): Promise { + return this.ordersService.updateTrackingAndCost(orderId, dto); + } } diff --git a/apps/backend/src/orders/order.entity.ts b/apps/backend/src/orders/order.entity.ts index 7c40fdb4..1cb49f84 100644 --- a/apps/backend/src/orders/order.entity.ts +++ b/apps/backend/src/orders/order.entity.ts @@ -77,4 +77,21 @@ export class Order { @OneToMany(() => Allocation, (allocation) => allocation.order) allocations: Allocation[]; + + @Column({ + name: 'tracking_link', + type: 'varchar', + length: 255, + nullable: true, + }) + trackingLink?: string; + + @Column({ + name: 'shipping_cost', + type: 'numeric', + precision: 10, + scale: 2, + nullable: true, + }) + shippingCost?: number; } diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 05f37bca..36975108 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -7,6 +7,7 @@ import { FoodManufacturer } from '../foodManufacturers/manufacturer.entity'; import { FoodRequest } from '../foodRequests/request.entity'; import { validateId } from '../utils/validation.utils'; import { OrderStatus } from './types'; +import { TrackingCostDto } from './dtos/tracking-cost.dto'; @Injectable() export class OrdersService { @@ -147,4 +148,18 @@ export class OrdersService { return orders; } + + async updateTrackingAndCost(orderId: number, dto: TrackingCostDto) { + validateId(orderId, 'Order'); + + await this.repo + .createQueryBuilder() + .update(Order) + .set({ + trackingLink: dto.trackingLink, + shippingCost: dto.shippingCost, + }) + .where('order_id = :orderId', { orderId }) + .execute(); + } } diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index eb98e3a2..8aba145b 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -198,6 +198,8 @@ export interface Order { createdAt: string; shippedAt: string | null; deliveredAt: string | null; + trackingLink: string | null; + shippingCost: number | null; } export interface OrderItemDetails {