update equipment stuff

This commit is contained in:
Jake Kasper
2025-08-22 09:41:59 -04:00
parent 96fe83412a
commit f20c23ffc7
4 changed files with 1010 additions and 15 deletions

View File

@@ -7,51 +7,72 @@ const { AppError } = require('../middleware/errorHandler');
const router = express.Router();
// @route GET /api/nozzles/types
// @desc Get all nozzle types
// @desc Get all nozzle types with flow rate data
// @access Private
router.get('/types', async (req, res, next) => {
try {
const { manufacturer, droplet_size, spray_pattern } = req.query;
const { manufacturer, droplet_size, spray_pattern, orifice_size } = req.query;
let query = 'SELECT * FROM nozzle_types WHERE 1=1';
let query = `
SELECT nt.*,
json_agg(
json_build_object(
'pressure_psi', nfr.pressure_psi,
'flow_rate_gpm', nfr.flow_rate_gpm
) ORDER BY nfr.pressure_psi
) FILTER (WHERE nfr.id IS NOT NULL) as flow_rates
FROM nozzle_types nt
LEFT JOIN nozzle_flow_rates nfr ON nt.id = nfr.nozzle_type_id
WHERE 1=1
`;
const queryParams = [];
if (manufacturer) {
queryParams.push(manufacturer);
query += ` AND manufacturer ILIKE $${queryParams.length}`;
query += ` AND nt.manufacturer ILIKE $${queryParams.length}`;
}
if (droplet_size) {
queryParams.push(droplet_size);
query += ` AND droplet_size = $${queryParams.length}`;
query += ` AND nt.droplet_size = $${queryParams.length}`;
}
if (spray_pattern) {
queryParams.push(spray_pattern);
query += ` AND spray_pattern = $${queryParams.length}`;
query += ` AND nt.spray_pattern = $${queryParams.length}`;
}
if (orifice_size) {
queryParams.push(orifice_size);
query += ` AND nt.orifice_size = $${queryParams.length}`;
}
query += ' ORDER BY manufacturer, name';
query += ' GROUP BY nt.id ORDER BY nt.manufacturer, nt.name';
const result = await pool.query(query, queryParams);
// Group by manufacturer for easier frontend handling
// Group by manufacturer and droplet size for easier frontend handling
const nozzlesByManufacturer = result.rows.reduce((acc, nozzle) => {
const manufacturer = nozzle.manufacturer || 'Unknown';
if (!acc[manufacturer]) {
acc[manufacturer] = [];
acc[manufacturer] = {};
}
acc[manufacturer].push({
if (!acc[manufacturer][nozzle.droplet_size]) {
acc[manufacturer][nozzle.droplet_size] = [];
}
acc[manufacturer][nozzle.droplet_size].push({
id: nozzle.id,
name: nozzle.name,
manufacturer: nozzle.manufacturer,
model: nozzle.model,
orificeSize: nozzle.orifice_size,
sprayAngle: nozzle.spray_angle,
flowRateGpm: parseFloat(nozzle.flow_rate_gpm),
dropletSize: nozzle.droplet_size,
sprayPattern: nozzle.spray_pattern,
pressureRangePsi: nozzle.pressure_range_psi,
material: nozzle.material,
threadSize: nozzle.thread_size,
colorCode: nozzle.color_code,
flowRates: nozzle.flow_rates || [],
createdAt: nozzle.created_at
});
return acc;
@@ -67,10 +88,12 @@ router.get('/types', async (req, res, next) => {
model: nt.model,
orificeSize: nt.orifice_size,
sprayAngle: nt.spray_angle,
flowRateGpm: parseFloat(nt.flow_rate_gpm),
dropletSize: nt.droplet_size,
sprayPattern: nt.spray_pattern,
pressureRangePsi: nt.pressure_range_psi,
material: nt.material,
threadSize: nt.thread_size,
colorCode: nt.color_code,
flowRates: nt.flow_rates || [],
createdAt: nt.created_at
})),
nozzlesByManufacturer
@@ -81,6 +104,63 @@ router.get('/types', async (req, res, next) => {
}
});
// @route GET /api/nozzles/flow-rate/:nozzleTypeId/:pressurePsi
// @desc Get flow rate for specific nozzle at specific pressure
// @access Private
router.get('/flow-rate/:nozzleTypeId/:pressurePsi', validateParams(idParamSchema), async (req, res, next) => {
try {
const { nozzleTypeId, pressurePsi } = req.params;
// Get exact match or interpolate between pressures
const result = await pool.query(
`SELECT nfr.pressure_psi, nfr.flow_rate_gpm, nt.name, nt.manufacturer
FROM nozzle_flow_rates nfr
JOIN nozzle_types nt ON nfr.nozzle_type_id = nt.id
WHERE nfr.nozzle_type_id = $1
ORDER BY ABS(nfr.pressure_psi - $2)
LIMIT 2`,
[nozzleTypeId, pressurePsi]
);
if (result.rows.length === 0) {
throw new AppError('Flow rate data not available for this nozzle', 404);
}
let flowRate;
if (result.rows.length === 1 || result.rows[0].pressure_psi === parseInt(pressurePsi)) {
// Exact match or only one data point
flowRate = parseFloat(result.rows[0].flow_rate_gpm);
} else {
// Interpolate between two points
const p1 = result.rows[0];
const p2 = result.rows[1];
const pressure = parseInt(pressurePsi);
if (p1.pressure_psi === p2.pressure_psi) {
flowRate = parseFloat(p1.flow_rate_gpm);
} else {
// Linear interpolation
const slope = (p2.flow_rate_gpm - p1.flow_rate_gpm) / (p2.pressure_psi - p1.pressure_psi);
flowRate = parseFloat(p1.flow_rate_gpm) + slope * (pressure - p1.pressure_psi);
flowRate = Math.round(flowRate * 1000) / 1000; // Round to 3 decimal places
}
}
res.json({
success: true,
data: {
nozzleTypeId: parseInt(nozzleTypeId),
pressurePsi: parseInt(pressurePsi),
flowRateGpm: flowRate,
nozzleName: result.rows[0].name,
manufacturer: result.rows[0].manufacturer
}
});
} catch (error) {
next(error);
}
});
// @route GET /api/nozzles
// @desc Get all user's nozzles
// @access Private
@@ -500,4 +580,250 @@ router.delete('/assignments/:assignmentId', validateParams(idParamSchema), async
}
});
// PUMP ASSIGNMENT ROUTES
// @route GET /api/nozzles/equipment/:equipmentId/pump
// @desc Get pump assigned to sprayer
// @access Private
router.get('/equipment/:equipmentId/pump', validateParams(idParamSchema), async (req, res, next) => {
try {
const equipmentId = req.params.equipmentId;
// Verify equipment belongs to user
const equipmentCheck = await pool.query(
'SELECT id FROM user_equipment WHERE id = $1 AND user_id = $2',
[equipmentId, req.user.id]
);
if (equipmentCheck.rows.length === 0) {
throw new AppError('Equipment not found', 404);
}
const result = await pool.query(
`SELECT epa.*,
pump_eq.custom_name as pump_name, pump_eq.manufacturer as pump_manufacturer,
pump_eq.model as pump_model, pump_eq.max_gpm, pump_eq.max_psi
FROM equipment_pump_assignments epa
JOIN user_equipment pump_eq ON epa.pump_equipment_id = pump_eq.id
WHERE epa.sprayer_equipment_id = $1 AND epa.is_active = true`,
[equipmentId]
);
res.json({
success: true,
data: {
pumpAssignment: result.rows.length > 0 ? {
id: result.rows[0].id,
sprayerEquipmentId: result.rows[0].sprayer_equipment_id,
pumpEquipmentId: result.rows[0].pump_equipment_id,
pumpName: result.rows[0].pump_name,
pumpManufacturer: result.rows[0].pump_manufacturer,
pumpModel: result.rows[0].pump_model,
maxGpm: parseFloat(result.rows[0].max_gpm) || null,
maxPsi: parseFloat(result.rows[0].max_psi) || null,
assignedDate: result.rows[0].assigned_date,
notes: result.rows[0].notes,
isActive: result.rows[0].is_active
} : null
}
});
} catch (error) {
next(error);
}
});
// @route POST /api/nozzles/equipment/:equipmentId/pump
// @desc Assign pump to sprayer
// @access Private
router.post('/equipment/:equipmentId/pump', validateParams(idParamSchema), async (req, res, next) => {
try {
const sprayerEquipmentId = req.params.equipmentId;
const { pumpEquipmentId, notes } = req.body;
// Verify both equipment belong to user
const equipmentCheck = await pool.query(
'SELECT id, category_id FROM user_equipment WHERE id IN ($1, $2) AND user_id = $3',
[sprayerEquipmentId, pumpEquipmentId, req.user.id]
);
if (equipmentCheck.rows.length !== 2) {
throw new AppError('Equipment not found', 404);
}
// Deactivate any existing pump assignments for this sprayer
await pool.query(
'UPDATE equipment_pump_assignments SET is_active = false WHERE sprayer_equipment_id = $1',
[sprayerEquipmentId]
);
// Create new assignment
const result = await pool.query(
`INSERT INTO equipment_pump_assignments
(sprayer_equipment_id, pump_equipment_id, notes)
VALUES ($1, $2, $3)
RETURNING *`,
[sprayerEquipmentId, pumpEquipmentId, notes]
);
const assignment = result.rows[0];
res.status(201).json({
success: true,
message: 'Pump assigned to sprayer successfully',
data: {
assignment: {
id: assignment.id,
sprayerEquipmentId: assignment.sprayer_equipment_id,
pumpEquipmentId: assignment.pump_equipment_id,
assignedDate: assignment.assigned_date,
notes: assignment.notes,
isActive: assignment.is_active
}
}
});
} catch (error) {
next(error);
}
});
// @route DELETE /api/nozzles/equipment/:equipmentId/pump
// @desc Remove pump assignment from sprayer
// @access Private
router.delete('/equipment/:equipmentId/pump', validateParams(idParamSchema), async (req, res, next) => {
try {
const sprayerEquipmentId = req.params.equipmentId;
// Verify sprayer belongs to user
const equipmentCheck = await pool.query(
'SELECT id FROM user_equipment WHERE id = $1 AND user_id = $2',
[sprayerEquipmentId, req.user.id]
);
if (equipmentCheck.rows.length === 0) {
throw new AppError('Equipment not found', 404);
}
await pool.query(
'UPDATE equipment_pump_assignments SET is_active = false WHERE sprayer_equipment_id = $1',
[sprayerEquipmentId]
);
res.json({
success: true,
message: 'Pump assignment removed successfully'
});
} catch (error) {
next(error);
}
});
// NOZZLE CONFIGURATION ROUTES
// @route GET /api/nozzles/equipment/:equipmentId/configurations
// @desc Get nozzle configurations for equipment
// @access Private
router.get('/equipment/:equipmentId/configurations', validateParams(idParamSchema), async (req, res, next) => {
try {
const equipmentId = req.params.equipmentId;
// Verify equipment belongs to user
const equipmentCheck = await pool.query(
'SELECT id FROM user_equipment WHERE id = $1 AND user_id = $2',
[equipmentId, req.user.id]
);
if (equipmentCheck.rows.length === 0) {
throw new AppError('Equipment not found', 404);
}
const result = await pool.query(
`SELECT enc.*,
COUNT(cna.id) as nozzle_count
FROM equipment_nozzle_configurations enc
LEFT JOIN configuration_nozzle_assignments cna ON enc.id = cna.configuration_id
WHERE enc.user_equipment_id = $1
GROUP BY enc.id
ORDER BY enc.is_default DESC, enc.configuration_name`,
[equipmentId]
);
res.json({
success: true,
data: {
configurations: result.rows.map(config => ({
id: config.id,
userEquipmentId: config.user_equipment_id,
configurationName: config.configuration_name,
isDefault: config.is_default,
nozzleCount: parseInt(config.nozzle_count),
createdAt: config.created_at,
updatedAt: config.updated_at
}))
}
});
} catch (error) {
next(error);
}
});
// @route GET /api/nozzles/configurations/:configId/details
// @desc Get detailed nozzle configuration with assignments
// @access Private
router.get('/configurations/:configId/details', validateParams(idParamSchema), async (req, res, next) => {
try {
const configId = req.params.configId;
// Verify configuration belongs to user's equipment
const configCheck = await pool.query(
`SELECT enc.* FROM equipment_nozzle_configurations enc
JOIN user_equipment ue ON enc.user_equipment_id = ue.id
WHERE enc.id = $1 AND ue.user_id = $2`,
[configId, req.user.id]
);
if (configCheck.rows.length === 0) {
throw new AppError('Configuration not found', 404);
}
const result = await pool.query(
`SELECT cna.*, nt.name, nt.manufacturer, nt.orifice_size, nt.spray_angle,
nt.droplet_size, nt.spray_pattern, nt.color_code,
nfr.flow_rate_gpm
FROM configuration_nozzle_assignments cna
JOIN nozzle_types nt ON cna.nozzle_type_id = nt.id
LEFT JOIN nozzle_flow_rates nfr ON nt.id = nfr.nozzle_type_id AND nfr.pressure_psi = cna.operating_pressure_psi
WHERE cna.configuration_id = $1
ORDER BY cna.position`,
[configId]
);
res.json({
success: true,
data: {
configuration: configCheck.rows[0],
assignments: result.rows.map(item => ({
id: item.id,
configurationId: item.configuration_id,
nozzleTypeId: item.nozzle_type_id,
position: item.position,
quantity: item.quantity,
operatingPressurePsi: item.operating_pressure_psi,
notes: item.notes,
nozzleName: item.name,
manufacturer: item.manufacturer,
orificeSize: item.orifice_size,
sprayAngle: item.spray_angle,
dropletSize: item.droplet_size,
sprayPattern: item.spray_pattern,
colorCode: item.color_code,
flowRateGpm: parseFloat(item.flow_rate_gpm) || null,
createdAt: item.created_at
}))
}
});
} catch (error) {
next(error);
}
});
module.exports = router;