165 lines
6.3 KiB
TypeScript
165 lines
6.3 KiB
TypeScript
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
|
|
|
const DEMO_PREFIX = '[DEMO]';
|
|
|
|
/**
|
|
* Admin routes for demo/testing operations
|
|
* These should only be available in development or testing environments
|
|
*/
|
|
export async function adminRoutes(fastify: FastifyInstance) {
|
|
// Auth check - require OWNER or ADMIN role
|
|
fastify.addHook('onRequest', async (request, reply) => {
|
|
try {
|
|
await request.jwtVerify();
|
|
const user = request.user as { role?: string };
|
|
if (!['OWNER', 'ADMIN'].includes(user?.role || '')) {
|
|
return reply.code(403).send({ error: 'Admin access required' });
|
|
}
|
|
} catch (err) {
|
|
return reply.code(401).send({ error: 'Unauthorized' });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Reseed demo plants in a realistic layout
|
|
* - Each batch stays in appropriate room based on stage
|
|
* - Veg batches → Veg rooms, Flower batches → Flower rooms, etc.
|
|
*/
|
|
fastify.post('/reseed-plants', async (request: FastifyRequest, reply: FastifyReply) => {
|
|
const prisma = fastify.prisma;
|
|
|
|
try {
|
|
fastify.log.info('🌿 Reseeding plants in realistic layout...');
|
|
|
|
// Clear existing plants
|
|
await prisma.facilityPlant.deleteMany({});
|
|
await prisma.facilityPosition.updateMany({
|
|
data: { status: 'EMPTY' }
|
|
});
|
|
|
|
// Get all demo batches
|
|
const batches = await prisma.batch.findMany({
|
|
where: { name: { startsWith: DEMO_PREFIX } },
|
|
orderBy: { startDate: 'asc' }
|
|
});
|
|
|
|
if (batches.length === 0) {
|
|
return reply.code(400).send({
|
|
error: 'No demo batches found. Run seed-demo.js first.'
|
|
});
|
|
}
|
|
|
|
// Section mapping by room type
|
|
const sectionMapping: Record<string, string[]> = {
|
|
VEG_PRIMARY: ['T1', 'T2', 'T3', 'T4'],
|
|
MOTHER: ['M1', 'M2'],
|
|
FLOWER_A: ['A1', 'A2', 'A3', 'A4', 'A5', 'A6'],
|
|
FLOWER_B: ['B1', 'B2', 'B3', 'B4', 'B5', 'B6'],
|
|
DRY: ['D1', 'D2'],
|
|
CURE: ['C1', 'C2'],
|
|
};
|
|
|
|
const results: { batch: string; planted: number }[] = [];
|
|
|
|
for (const batch of batches) {
|
|
let targetSections: string[] = [];
|
|
let fillPercentage = 0.85;
|
|
|
|
switch (batch.stage) {
|
|
case 'CLONE_IN':
|
|
case 'VEGETATIVE':
|
|
targetSections = sectionMapping.VEG_PRIMARY;
|
|
fillPercentage = 0.9;
|
|
break;
|
|
case 'FLOWERING':
|
|
if (batch.strain === 'Gorilla Glue #4') {
|
|
targetSections = sectionMapping.FLOWER_A;
|
|
} else if (batch.strain === 'Wedding Cake') {
|
|
targetSections = sectionMapping.FLOWER_B;
|
|
} else {
|
|
targetSections = ['A4', 'A5', 'A6'];
|
|
}
|
|
fillPercentage = 0.95;
|
|
break;
|
|
case 'DRYING':
|
|
targetSections = sectionMapping.DRY;
|
|
fillPercentage = 0.6;
|
|
break;
|
|
case 'CURING':
|
|
targetSections = sectionMapping.CURE;
|
|
fillPercentage = 0.5;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
// Get sections
|
|
const sections = await prisma.facilitySection.findMany({
|
|
where: { code: { in: targetSections } },
|
|
include: { positions: true }
|
|
});
|
|
|
|
// Collect available positions
|
|
let allPositions: any[] = [];
|
|
for (const section of sections) {
|
|
const available = section.positions.filter((p: any) => p.status === 'EMPTY');
|
|
allPositions = allPositions.concat(
|
|
available.map((p: any) => ({ ...p, sectionCode: section.code }))
|
|
);
|
|
}
|
|
|
|
const targetCount = Math.min(
|
|
Math.floor(allPositions.length * fillPercentage),
|
|
batch.plantCount || allPositions.length
|
|
);
|
|
|
|
const plantsToCreate = [];
|
|
const positionIds = [];
|
|
|
|
for (let i = 0; i < targetCount && i < allPositions.length; i++) {
|
|
const pos = allPositions[i];
|
|
const tagPrefix = batch.strain?.substring(0, 2).toUpperCase() || 'XX';
|
|
const tagNumber = `1A4-${tagPrefix}-${batch.id.substring(0, 4).toUpperCase()}-${String(i + 1).padStart(4, '0')}`;
|
|
|
|
plantsToCreate.push({
|
|
tagNumber,
|
|
batchId: batch.id,
|
|
positionId: pos.id,
|
|
address: `${pos.sectionCode}-R${pos.row}-C${pos.column}`,
|
|
status: 'ACTIVE'
|
|
});
|
|
positionIds.push(pos.id);
|
|
}
|
|
|
|
if (plantsToCreate.length > 0) {
|
|
await prisma.facilityPlant.createMany({
|
|
data: plantsToCreate,
|
|
skipDuplicates: true
|
|
});
|
|
await prisma.facilityPosition.updateMany({
|
|
where: { id: { in: positionIds } },
|
|
data: { status: 'OCCUPIED' }
|
|
});
|
|
}
|
|
|
|
results.push({ batch: batch.name || batch.id, planted: plantsToCreate.length });
|
|
}
|
|
|
|
const totalPlants = await prisma.facilityPlant.count();
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Plants reseeded in realistic layout',
|
|
totalPlants,
|
|
batches: results
|
|
};
|
|
|
|
} catch (error) {
|
|
fastify.log.error(error);
|
|
return reply.code(500).send({
|
|
error: 'Failed to reseed plants',
|
|
details: error instanceof Error ? error.message : 'Unknown error'
|
|
});
|
|
}
|
|
});
|
|
}
|