feat: Shopping List Backend API Complete
📦 Supply Items Backend (Phase 3A) ✅ Controller (supplies.controller.ts): - getSupplyItems() - List all items - getShoppingList() - Items below threshold - getSupplyItem() - Single item detail - createSupplyItem() - Add new item - updateSupplyItem() - Edit item - deleteSupplyItem() - Remove item - markAsOrdered() - Track last ordered date - adjustQuantity() - Add/subtract stock ✅ Routes (supplies.routes.ts): - GET /api/supplies - GET /api/supplies/shopping-list - GET /api/supplies/:id - POST /api/supplies - PATCH /api/supplies/:id - DELETE /api/supplies/:id - POST /api/supplies/:id/order - POST /api/supplies/:id/adjust ✅ Server Integration: - Registered supplies routes Next: Frontend UI + Migration
This commit is contained in:
parent
d42331075d
commit
22574359ba
3 changed files with 232 additions and 0 deletions
193
backend/src/controllers/supplies.controller.ts
Normal file
193
backend/src/controllers/supplies.controller.ts
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Get all supply items
|
||||
export async function getSupplyItems(request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const items = await prisma.supplyItem.findMany({
|
||||
orderBy: [
|
||||
{ category: 'asc' },
|
||||
{ name: 'asc' },
|
||||
],
|
||||
});
|
||||
|
||||
return reply.send(items);
|
||||
} catch (error: any) {
|
||||
return reply.status(500).send({ message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Get shopping list (items below threshold)
|
||||
export async function getShoppingList(request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const items = await prisma.supplyItem.findMany({
|
||||
where: {
|
||||
quantity: {
|
||||
lte: prisma.supplyItem.fields.minThreshold,
|
||||
},
|
||||
},
|
||||
orderBy: [
|
||||
{ category: 'asc' },
|
||||
{ name: 'asc' },
|
||||
],
|
||||
});
|
||||
|
||||
return reply.send(items);
|
||||
} catch (error: any) {
|
||||
return reply.status(500).send({ message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Get single supply item
|
||||
export async function getSupplyItem(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
|
||||
const item = await prisma.supplyItem.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!item) {
|
||||
return reply.status(404).send({ message: 'Supply item not found' });
|
||||
}
|
||||
|
||||
return reply.send(item);
|
||||
} catch (error: any) {
|
||||
return reply.status(500).send({ message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Create supply item
|
||||
export async function createSupplyItem(
|
||||
request: FastifyRequest<{
|
||||
Body: {
|
||||
name: string;
|
||||
category: string;
|
||||
quantity?: number;
|
||||
minThreshold?: number;
|
||||
unit: string;
|
||||
location?: string;
|
||||
notes?: string;
|
||||
};
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const { name, category, quantity, minThreshold, unit, location, notes } = request.body;
|
||||
|
||||
const item = await prisma.supplyItem.create({
|
||||
data: {
|
||||
name,
|
||||
category: category as any,
|
||||
quantity: quantity || 0,
|
||||
minThreshold: minThreshold || 0,
|
||||
unit,
|
||||
location,
|
||||
notes,
|
||||
},
|
||||
});
|
||||
|
||||
return reply.status(201).send(item);
|
||||
} catch (error: any) {
|
||||
return reply.status(500).send({ message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Update supply item
|
||||
export async function updateSupplyItem(
|
||||
request: FastifyRequest<{
|
||||
Params: { id: string };
|
||||
Body: {
|
||||
name?: string;
|
||||
category?: string;
|
||||
quantity?: number;
|
||||
minThreshold?: number;
|
||||
unit?: string;
|
||||
location?: string;
|
||||
notes?: string;
|
||||
};
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const data = request.body;
|
||||
|
||||
const item = await prisma.supplyItem.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...data,
|
||||
category: data.category as any,
|
||||
},
|
||||
});
|
||||
|
||||
return reply.send(item);
|
||||
} catch (error: any) {
|
||||
return reply.status(500).send({ message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Delete supply item
|
||||
export async function deleteSupplyItem(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
|
||||
await prisma.supplyItem.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return reply.status(204).send();
|
||||
} catch (error: any) {
|
||||
return reply.status(500).send({ message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Mark item as ordered
|
||||
export async function markAsOrdered(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
|
||||
const item = await prisma.supplyItem.update({
|
||||
where: { id },
|
||||
data: {
|
||||
lastOrdered: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
return reply.send(item);
|
||||
} catch (error: any) {
|
||||
return reply.status(500).send({ message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust quantity (add or subtract)
|
||||
export async function adjustQuantity(
|
||||
request: FastifyRequest<{
|
||||
Params: { id: string };
|
||||
Body: { adjustment: number };
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { adjustment } = request.body;
|
||||
|
||||
const item = await prisma.supplyItem.findUnique({ where: { id } });
|
||||
if (!item) {
|
||||
return reply.status(404).send({ message: 'Supply item not found' });
|
||||
}
|
||||
|
||||
const updated = await prisma.supplyItem.update({
|
||||
where: { id },
|
||||
data: {
|
||||
quantity: Math.max(0, item.quantity + adjustment),
|
||||
},
|
||||
});
|
||||
|
||||
return reply.send(updated);
|
||||
} catch (error: any) {
|
||||
return reply.status(500).send({ message: error.message });
|
||||
}
|
||||
}
|
||||
37
backend/src/routes/supplies.routes.ts
Normal file
37
backend/src/routes/supplies.routes.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
import {
|
||||
getSupplyItems,
|
||||
getShoppingList,
|
||||
getSupplyItem,
|
||||
createSupplyItem,
|
||||
updateSupplyItem,
|
||||
deleteSupplyItem,
|
||||
markAsOrdered,
|
||||
adjustQuantity,
|
||||
} from '../controllers/supplies.controller';
|
||||
|
||||
export async function suppliesRoutes(server: FastifyInstance) {
|
||||
// Get all supply items
|
||||
server.get('/supplies', getSupplyItems);
|
||||
|
||||
// Get shopping list (items below threshold)
|
||||
server.get('/supplies/shopping-list', getShoppingList);
|
||||
|
||||
// Get single supply item
|
||||
server.get('/supplies/:id', getSupplyItem);
|
||||
|
||||
// Create supply item
|
||||
server.post('/supplies', createSupplyItem);
|
||||
|
||||
// Update supply item
|
||||
server.patch('/supplies/:id', updateSupplyItem);
|
||||
|
||||
// Delete supply item
|
||||
server.delete('/supplies/:id', deleteSupplyItem);
|
||||
|
||||
// Mark item as ordered
|
||||
server.post('/supplies/:id/order', markAsOrdered);
|
||||
|
||||
// Adjust quantity
|
||||
server.post('/supplies/:id/adjust', adjustQuantity);
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import { roomRoutes } from './routes/rooms.routes';
|
|||
import { batchRoutes } from './routes/batches.routes';
|
||||
import { timeclockRoutes } from './routes/timeclock.routes';
|
||||
import { walkthroughRoutes } from './routes/walkthrough.routes';
|
||||
import { suppliesRoutes } from './routes/supplies.routes';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ server.register(roomRoutes, { prefix: '/api/rooms' });
|
|||
server.register(batchRoutes, { prefix: '/api/batches' });
|
||||
server.register(timeclockRoutes, { prefix: '/api/timeclock' });
|
||||
server.register(walkthroughRoutes, { prefix: '/api/walkthroughs' });
|
||||
server.register(suppliesRoutes, { prefix: '/api' });
|
||||
|
||||
server.get('/api/healthz', async (request, reply) => {
|
||||
return { status: 'ok', timestamp: new Date().toISOString() };
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue