323 lines
9.4 KiB
TypeScript
323 lines
9.4 KiB
TypeScript
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
|
|
interface CreateWalkthroughBody {
|
|
date?: string;
|
|
}
|
|
|
|
interface AddReservoirCheckBody {
|
|
tankName: string;
|
|
tankType: 'VEG' | 'FLOWER';
|
|
levelPercent: number;
|
|
status: 'OK' | 'LOW' | 'CRITICAL';
|
|
photoUrl?: string;
|
|
notes?: string;
|
|
}
|
|
|
|
interface AddIrrigationCheckBody {
|
|
zoneName: string;
|
|
drippersTotal: number;
|
|
drippersWorking: number;
|
|
drippersFailed?: string[];
|
|
waterFlow: boolean;
|
|
nutrientsMixed: boolean;
|
|
scheduleActive: boolean;
|
|
photoUrl?: string;
|
|
issues?: string;
|
|
}
|
|
|
|
interface AddPlantHealthCheckBody {
|
|
zoneName: string;
|
|
healthStatus: 'GOOD' | 'FAIR' | 'NEEDS_ATTENTION';
|
|
pestsObserved: boolean;
|
|
pestType?: string;
|
|
waterAccess: 'OK' | 'ISSUES';
|
|
foodAccess: 'OK' | 'ISSUES';
|
|
flaggedForAttention?: boolean;
|
|
issuePhotoUrl?: string;
|
|
referencePhotoUrl?: string;
|
|
notes?: string;
|
|
}
|
|
|
|
/**
|
|
* Start a new daily walkthrough
|
|
*/
|
|
export const createWalkthrough = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
const { date } = (request.body as CreateWalkthroughBody) || {};
|
|
const userId = (request.user as any)?.userId;
|
|
|
|
if (!userId) {
|
|
return reply.code(401).send({ message: 'Unauthorized' });
|
|
}
|
|
|
|
// Verify user exists (handle stale tokens after re-seed)
|
|
const userExists = await request.server.prisma.user.findUnique({
|
|
where: { id: userId }
|
|
});
|
|
|
|
if (!userExists) {
|
|
return reply.code(401).send({ message: 'User not found. Please log in again.' });
|
|
}
|
|
|
|
try {
|
|
const walkthrough = await request.server.prisma.dailyWalkthrough.create({
|
|
data: {
|
|
date: date ? new Date(date) : new Date(),
|
|
completedBy: userId,
|
|
status: 'IN_PROGRESS',
|
|
},
|
|
|
|
include: {
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
email: true,
|
|
role: true,
|
|
},
|
|
},
|
|
reservoirChecks: true,
|
|
irrigationChecks: true,
|
|
plantHealthChecks: true,
|
|
},
|
|
});
|
|
|
|
return reply.code(201).send(walkthrough);
|
|
} catch (error) {
|
|
request.log.error(error);
|
|
return reply.code(500).send({ message: 'Failed to create walkthrough' });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get all walkthroughs (with optional filters)
|
|
*/
|
|
export const getWalkthroughs = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
const { status, startDate, endDate, userId } = request.query as any;
|
|
|
|
try {
|
|
const walkthroughs = await request.server.prisma.dailyWalkthrough.findMany({
|
|
where: {
|
|
...(status && { status }),
|
|
...(userId && { completedBy: userId }),
|
|
...(startDate && endDate && {
|
|
date: {
|
|
gte: new Date(startDate),
|
|
lte: new Date(endDate),
|
|
},
|
|
}),
|
|
},
|
|
include: {
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
email: true,
|
|
role: true,
|
|
},
|
|
},
|
|
reservoirChecks: true,
|
|
irrigationChecks: true,
|
|
plantHealthChecks: true,
|
|
},
|
|
orderBy: {
|
|
date: 'desc',
|
|
},
|
|
});
|
|
|
|
return walkthroughs;
|
|
} catch (error) {
|
|
request.log.error(error);
|
|
return reply.code(500).send({ message: 'Failed to fetch walkthroughs' });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get today's walkthrough (if exists)
|
|
*/
|
|
export const getTodaysWalkthrough = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
try {
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
const tomorrow = new Date(today);
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
const walkthrough = await request.server.prisma.dailyWalkthrough.findFirst({
|
|
where: {
|
|
date: {
|
|
gte: today,
|
|
lt: tomorrow,
|
|
},
|
|
},
|
|
include: {
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
email: true,
|
|
role: true,
|
|
},
|
|
},
|
|
reservoirChecks: true,
|
|
irrigationChecks: true,
|
|
plantHealthChecks: true,
|
|
},
|
|
orderBy: {
|
|
startTime: 'desc',
|
|
},
|
|
});
|
|
|
|
if (!walkthrough) {
|
|
return reply.code(404).send({ message: 'No walkthrough found for today' });
|
|
}
|
|
|
|
return walkthrough;
|
|
} catch (error) {
|
|
request.log.error(error);
|
|
return reply.code(500).send({ message: 'Failed to fetch today\'s walkthrough' });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a single walkthrough by ID
|
|
*/
|
|
export const getWalkthrough = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
const { id } = request.params as { id: string };
|
|
|
|
try {
|
|
const walkthrough = await request.server.prisma.dailyWalkthrough.findUnique({
|
|
where: { id },
|
|
include: {
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
email: true,
|
|
role: true,
|
|
},
|
|
},
|
|
reservoirChecks: true,
|
|
irrigationChecks: true,
|
|
plantHealthChecks: true,
|
|
},
|
|
});
|
|
|
|
if (!walkthrough) {
|
|
return reply.code(404).send({ message: 'Walkthrough not found' });
|
|
}
|
|
|
|
return walkthrough;
|
|
} catch (error) {
|
|
request.log.error(error);
|
|
return reply.code(500).send({ message: 'Failed to fetch walkthrough' });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Complete a walkthrough
|
|
*/
|
|
export const completeWalkthrough = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
const { id } = request.params as { id: string };
|
|
|
|
try {
|
|
const walkthrough = await request.server.prisma.dailyWalkthrough.update({
|
|
where: { id },
|
|
data: {
|
|
status: 'COMPLETED',
|
|
endTime: new Date(),
|
|
},
|
|
include: {
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
email: true,
|
|
role: true,
|
|
},
|
|
},
|
|
reservoirChecks: true,
|
|
irrigationChecks: true,
|
|
plantHealthChecks: true,
|
|
},
|
|
});
|
|
|
|
return walkthrough;
|
|
} catch (error) {
|
|
request.log.error(error);
|
|
return reply.code(500).send({ message: 'Failed to complete walkthrough' });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add a reservoir check to a walkthrough
|
|
*/
|
|
export const addReservoirCheck = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
const { id } = request.params as { id: string };
|
|
const checkData = request.body as AddReservoirCheckBody;
|
|
|
|
try {
|
|
const check = await request.server.prisma.reservoirCheck.create({
|
|
data: {
|
|
walkthroughId: id,
|
|
...checkData,
|
|
},
|
|
});
|
|
|
|
return reply.code(201).send(check);
|
|
} catch (error) {
|
|
request.log.error(error);
|
|
return reply.code(500).send({ message: 'Failed to add reservoir check' });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add an irrigation check to a walkthrough
|
|
*/
|
|
export const addIrrigationCheck = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
const { id } = request.params as { id: string };
|
|
const checkData = request.body as AddIrrigationCheckBody;
|
|
|
|
try {
|
|
const check = await request.server.prisma.irrigationCheck.create({
|
|
data: {
|
|
walkthroughId: id,
|
|
zoneName: checkData.zoneName,
|
|
drippersTotal: checkData.drippersTotal,
|
|
drippersWorking: checkData.drippersWorking,
|
|
drippersFailed: checkData.drippersFailed ? JSON.stringify(checkData.drippersFailed) : null,
|
|
waterFlow: checkData.waterFlow,
|
|
nutrientsMixed: checkData.nutrientsMixed,
|
|
scheduleActive: checkData.scheduleActive,
|
|
photoUrl: checkData.photoUrl,
|
|
issues: checkData.issues,
|
|
},
|
|
});
|
|
|
|
return reply.code(201).send(check);
|
|
} catch (error) {
|
|
request.log.error(error);
|
|
return reply.code(500).send({ message: 'Failed to add irrigation check' });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add a plant health check to a walkthrough
|
|
*/
|
|
export const addPlantHealthCheck = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
const { id } = request.params as { id: string };
|
|
const checkData = request.body as AddPlantHealthCheckBody;
|
|
|
|
try {
|
|
const check = await request.server.prisma.plantHealthCheck.create({
|
|
data: {
|
|
walkthroughId: id,
|
|
...checkData,
|
|
flaggedForAttention: checkData.flaggedForAttention || false,
|
|
},
|
|
});
|
|
|
|
return reply.code(201).send(check);
|
|
} catch (error) {
|
|
request.log.error(error);
|
|
return reply.code(500).send({ message: 'Failed to add plant health check' });
|
|
}
|
|
};
|