diff --git a/src/supply/supply.controller.ts b/src/supply/supply.controller.ts index 55133484..1acca273 100644 --- a/src/supply/supply.controller.ts +++ b/src/supply/supply.controller.ts @@ -1,12 +1,15 @@ import { Body, Controller, + DefaultValuePipe, Get, HttpException, Logger, Param, + ParseIntPipe, Post, Put, + Query, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -52,4 +55,27 @@ export class SupplyController { throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); } } + + @Get('surplus-demand-matches') + async surplus_demand_matches( + @Query('supplyId') supplyId: string, + @Query('shelterIdSurplus') shelterIdSurplus: string, + @Query('shelterIdNeeded') shelterIdNeeded: string, + @Query('perPage', new DefaultValuePipe(20), ParseIntPipe) perPage: number, + @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, + ) { + try { + const data = await this.supplyServices.surplus_demand_matches({ + supplyId, + shelterIdSurplus, + shelterIdNeeded, + perPage, + page + }); + return new ServerResponse(200, 'Successfully got matches between surplus and demand', data); + } catch (err: any) { + this.logger.error(`Failed to get matches: ${err}`); + throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); + } + } } diff --git a/src/supply/supply.service.ts b/src/supply/supply.service.ts index a3caf57e..bb83f5f1 100644 --- a/src/supply/supply.service.ts +++ b/src/supply/supply.service.ts @@ -1,8 +1,9 @@ import z from 'zod'; import { Injectable } from '@nestjs/common'; +import { Prisma } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; -import { CreateSupplySchema, UpdateSupplySchema } from './types'; +import { CreateSupplySchema, SurplusDemandMatch, UpdateSupplySchema } from './types'; @Injectable() export class SupplyService { @@ -53,4 +54,71 @@ export class SupplyService { return data; } + + async surplus_demand_matches(body: z.infer) { + const payload = SurplusDemandMatch.parse(body); + const skip = payload.perPage * (payload.page - 1); + + let where: Prisma.Sql[] = []; + + if (payload.supplyId) { + if (where.length == 0) { + where.push(Prisma.sql`WHERE spl.supply_id = ${payload.supplyId}`); + } else { + where.push(Prisma.sql`spl.supply_id = ${payload.supplyId}`); + } + } + if (payload.shelterIdSurplus) { + if (where.length == 0) { + where.push(Prisma.sql`WHERE spl.shelter_id = ${payload.shelterIdSurplus}`); + } else { + where.push(Prisma.sql`spl.shelter_id = ${payload.shelterIdSurplus}`); + } + } + if (payload.shelterIdNeeded) { + if (where.length == 0) { + where.push(Prisma.sql`WHERE sd.shelter_id = ${payload.shelterIdNeeded}`); + } else { + where.push(Prisma.sql`sd.shelter_id = ${payload.shelterIdNeeded}`); + } + } + + const query = Prisma.sql` + WITH supply_surplus AS ( + SELECT + ss.supply_id, + sp.name AS supply_name, + s.id AS shelter_id, + s.name AS shelter_name + FROM shelter_supplies ss + INNER JOIN shelters s ON s.id = ss.shelter_id + INNER JOIN supplies sp ON sp.id = ss.supply_id + WHERE ss.priority IN (1) -- 1 is 'Remaining' + ), supply_demand AS ( + SELECT + ss.supply_id, + sp.name AS supply_name, + s.id AS shelter_id, + s.name AS shelter_name + FROM shelter_supplies ss + INNER JOIN shelters s ON s.id = ss.shelter_id + INNER JOIN supplies sp ON sp.id = ss.supply_id + WHERE ss.priority IN (10, 100) -- 10 & 100 are 'Needing' & 'Urgent' + ) + SELECT + spl.supply_id, + spl.supply_name, + spl.shelter_id AS shelter_from_id, + spl.shelter_name AS shelter_from_name, + sd.shelter_id AS shelter_to_id, + sd.shelter_name AS shelter_to_name + FROM supply_surplus spl + INNER JOIN supply_demand sd ON sd.supply_id = spl.supply_id + ${where.length > 0 ? Prisma.join(where, ' AND ') : Prisma.empty} + ORDER BY spl.supply_name DESC, shelter_from_name DESC, shelter_to_name DESC + LIMIT ${payload.perPage} OFFSET ${skip} + `; + + return await this.prismaService.$queryRaw(query); + } } diff --git a/src/supply/types.ts b/src/supply/types.ts index faef2d11..2e09b2fe 100644 --- a/src/supply/types.ts +++ b/src/supply/types.ts @@ -27,4 +27,21 @@ const UpdateSupplySchema = SupplySchema.pick({ supplyCategoryId: true, }).partial(); -export { SupplySchema, CreateSupplySchema, UpdateSupplySchema, SupplyPriority }; +const SurplusDemandMatch = z.object({ + supplyId: z.string().nullable().optional(), + shelterIdSurplus: z.string().nullable().optional(), + shelterIdNeeded: z.string().nullable().optional(), + page: z.preprocess((v) => +((v ?? '1') as string), z.number().min(1)), + perPage: z.preprocess( + (v) => +((v ?? '10') as string), + z.number().min(1).max(100), + ), +}); + +export { + SupplySchema, + CreateSupplySchema, + UpdateSupplySchema, + SurplusDemandMatch, + SupplyPriority +};