829 lines
26 KiB
JavaScript
829 lines
26 KiB
JavaScript
const express = require('express');
|
|
const pool = require('../config/database');
|
|
const { validateRequest, validateParams } = require('../utils/validation');
|
|
const { idParamSchema } = require('../utils/validation');
|
|
const { AppError } = require('../middleware/errorHandler');
|
|
|
|
const router = express.Router();
|
|
|
|
// @route GET /api/nozzles/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, orifice_size } = req.query;
|
|
|
|
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 nt.manufacturer ILIKE $${queryParams.length}`;
|
|
}
|
|
|
|
if (droplet_size) {
|
|
queryParams.push(droplet_size);
|
|
query += ` AND nt.droplet_size = $${queryParams.length}`;
|
|
}
|
|
|
|
if (spray_pattern) {
|
|
queryParams.push(spray_pattern);
|
|
query += ` AND nt.spray_pattern = $${queryParams.length}`;
|
|
}
|
|
|
|
if (orifice_size) {
|
|
queryParams.push(orifice_size);
|
|
query += ` AND nt.orifice_size = $${queryParams.length}`;
|
|
}
|
|
|
|
query += ' GROUP BY nt.id ORDER BY nt.manufacturer, nt.name';
|
|
|
|
const result = await pool.query(query, queryParams);
|
|
|
|
// 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] = {};
|
|
}
|
|
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,
|
|
dropletSize: nozzle.droplet_size,
|
|
sprayPattern: nozzle.spray_pattern,
|
|
material: nozzle.material,
|
|
threadSize: nozzle.thread_size,
|
|
colorCode: nozzle.color_code,
|
|
flowRates: nozzle.flow_rates || [],
|
|
createdAt: nozzle.created_at
|
|
});
|
|
return acc;
|
|
}, {});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
nozzleTypes: result.rows.map(nt => ({
|
|
id: nt.id,
|
|
name: nt.name,
|
|
manufacturer: nt.manufacturer,
|
|
model: nt.model,
|
|
orificeSize: nt.orifice_size,
|
|
sprayAngle: nt.spray_angle,
|
|
dropletSize: nt.droplet_size,
|
|
sprayPattern: nt.spray_pattern,
|
|
material: nt.material,
|
|
threadSize: nt.thread_size,
|
|
colorCode: nt.color_code,
|
|
flowRates: nt.flow_rates || [],
|
|
createdAt: nt.created_at
|
|
})),
|
|
nozzlesByManufacturer
|
|
}
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// @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
|
|
router.get('/', async (req, res, next) => {
|
|
try {
|
|
const result = await pool.query(
|
|
`SELECT un.*, nt.name as type_name, nt.manufacturer as type_manufacturer,
|
|
nt.model as type_model, nt.orifice_size, nt.spray_angle, nt.flow_rate_gpm,
|
|
nt.droplet_size, nt.spray_pattern, nt.pressure_range_psi
|
|
FROM user_nozzles un
|
|
LEFT JOIN nozzle_types nt ON un.nozzle_type_id = nt.id
|
|
WHERE un.user_id = $1
|
|
ORDER BY nt.manufacturer, nt.name, un.custom_name`,
|
|
[req.user.id]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
nozzles: result.rows.map(item => ({
|
|
id: item.id,
|
|
nozzleTypeId: item.nozzle_type_id,
|
|
typeName: item.type_name,
|
|
typeManufacturer: item.type_manufacturer,
|
|
typeModel: item.type_model,
|
|
orificeSize: item.orifice_size,
|
|
sprayAngle: item.spray_angle,
|
|
flowRateGpm: parseFloat(item.flow_rate_gpm),
|
|
dropletSize: item.droplet_size,
|
|
sprayPattern: item.spray_pattern,
|
|
pressureRangePsi: item.pressure_range_psi,
|
|
customName: item.custom_name,
|
|
quantity: item.quantity,
|
|
condition: item.condition,
|
|
purchaseDate: item.purchase_date,
|
|
notes: item.notes,
|
|
createdAt: item.created_at,
|
|
updatedAt: item.updated_at
|
|
}))
|
|
}
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// @route GET /api/nozzles/:id
|
|
// @desc Get single user nozzle
|
|
// @access Private
|
|
router.get('/:id', validateParams(idParamSchema), async (req, res, next) => {
|
|
try {
|
|
const nozzleId = req.params.id;
|
|
|
|
const result = await pool.query(
|
|
`SELECT un.*, nt.name as type_name, nt.manufacturer as type_manufacturer,
|
|
nt.model as type_model, nt.orifice_size, nt.spray_angle, nt.flow_rate_gpm,
|
|
nt.droplet_size, nt.spray_pattern, nt.pressure_range_psi
|
|
FROM user_nozzles un
|
|
LEFT JOIN nozzle_types nt ON un.nozzle_type_id = nt.id
|
|
WHERE un.id = $1 AND un.user_id = $2`,
|
|
[nozzleId, req.user.id]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
throw new AppError('Nozzle not found', 404);
|
|
}
|
|
|
|
const item = result.rows[0];
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
nozzle: {
|
|
id: item.id,
|
|
nozzleTypeId: item.nozzle_type_id,
|
|
typeName: item.type_name,
|
|
typeManufacturer: item.type_manufacturer,
|
|
typeModel: item.type_model,
|
|
orificeSize: item.orifice_size,
|
|
sprayAngle: item.spray_angle,
|
|
flowRateGpm: parseFloat(item.flow_rate_gpm),
|
|
dropletSize: item.droplet_size,
|
|
sprayPattern: item.spray_pattern,
|
|
pressureRangePsi: item.pressure_range_psi,
|
|
customName: item.custom_name,
|
|
quantity: item.quantity,
|
|
condition: item.condition,
|
|
purchaseDate: item.purchase_date,
|
|
notes: item.notes,
|
|
createdAt: item.created_at,
|
|
updatedAt: item.updated_at
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// @route POST /api/nozzles
|
|
// @desc Add new nozzle to user's inventory
|
|
// @access Private
|
|
router.post('/', async (req, res, next) => {
|
|
try {
|
|
const {
|
|
nozzleTypeId,
|
|
customName,
|
|
quantity,
|
|
condition,
|
|
purchaseDate,
|
|
notes
|
|
} = req.body;
|
|
|
|
// Validate nozzle type exists if provided
|
|
if (nozzleTypeId) {
|
|
const typeCheck = await pool.query(
|
|
'SELECT id, name, manufacturer FROM nozzle_types WHERE id = $1',
|
|
[nozzleTypeId]
|
|
);
|
|
|
|
if (typeCheck.rows.length === 0) {
|
|
throw new AppError('Nozzle type not found', 404);
|
|
}
|
|
}
|
|
|
|
// Either nozzleTypeId or customName is required
|
|
if (!nozzleTypeId && !customName) {
|
|
throw new AppError('Either nozzle type or custom name is required', 400);
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO user_nozzles
|
|
(user_id, nozzle_type_id, custom_name, quantity, condition, purchase_date, notes)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING *`,
|
|
[req.user.id, nozzleTypeId, customName, quantity || 1, condition || 'good', purchaseDate, notes]
|
|
);
|
|
|
|
const nozzle = result.rows[0];
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Nozzle added successfully',
|
|
data: {
|
|
nozzle: {
|
|
id: nozzle.id,
|
|
nozzleTypeId: nozzle.nozzle_type_id,
|
|
customName: nozzle.custom_name,
|
|
quantity: nozzle.quantity,
|
|
condition: nozzle.condition,
|
|
purchaseDate: nozzle.purchase_date,
|
|
notes: nozzle.notes,
|
|
createdAt: nozzle.created_at,
|
|
updatedAt: nozzle.updated_at
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// @route PUT /api/nozzles/:id
|
|
// @desc Update user nozzle
|
|
// @access Private
|
|
router.put('/:id', validateParams(idParamSchema), async (req, res, next) => {
|
|
try {
|
|
const nozzleId = req.params.id;
|
|
const {
|
|
nozzleTypeId,
|
|
customName,
|
|
quantity,
|
|
condition,
|
|
purchaseDate,
|
|
notes
|
|
} = req.body;
|
|
|
|
// Check if nozzle exists and belongs to user
|
|
const checkResult = await pool.query(
|
|
'SELECT id FROM user_nozzles WHERE id = $1 AND user_id = $2',
|
|
[nozzleId, req.user.id]
|
|
);
|
|
|
|
if (checkResult.rows.length === 0) {
|
|
throw new AppError('Nozzle not found', 404);
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`UPDATE user_nozzles
|
|
SET nozzle_type_id = $1, custom_name = $2, quantity = $3, condition = $4,
|
|
purchase_date = $5, notes = $6, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $7
|
|
RETURNING *`,
|
|
[nozzleTypeId, customName, quantity, condition, purchaseDate, notes, nozzleId]
|
|
);
|
|
|
|
const nozzle = result.rows[0];
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Nozzle updated successfully',
|
|
data: {
|
|
nozzle: {
|
|
id: nozzle.id,
|
|
nozzleTypeId: nozzle.nozzle_type_id,
|
|
customName: nozzle.custom_name,
|
|
quantity: nozzle.quantity,
|
|
condition: nozzle.condition,
|
|
purchaseDate: nozzle.purchase_date,
|
|
notes: nozzle.notes,
|
|
createdAt: nozzle.created_at,
|
|
updatedAt: nozzle.updated_at
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// @route DELETE /api/nozzles/:id
|
|
// @desc Delete user nozzle
|
|
// @access Private
|
|
router.delete('/:id', validateParams(idParamSchema), async (req, res, next) => {
|
|
try {
|
|
const nozzleId = req.params.id;
|
|
|
|
// Check if nozzle exists and belongs to user
|
|
const checkResult = await pool.query(
|
|
'SELECT id FROM user_nozzles WHERE id = $1 AND user_id = $2',
|
|
[nozzleId, req.user.id]
|
|
);
|
|
|
|
if (checkResult.rows.length === 0) {
|
|
throw new AppError('Nozzle not found', 404);
|
|
}
|
|
|
|
// Check if nozzle is assigned to any equipment
|
|
const assignmentCheck = await pool.query(
|
|
'SELECT COUNT(*) as count FROM equipment_nozzle_assignments WHERE user_nozzle_id = $1',
|
|
[nozzleId]
|
|
);
|
|
|
|
if (parseInt(assignmentCheck.rows[0].count) > 0) {
|
|
throw new AppError('Cannot delete nozzle that is assigned to equipment', 400);
|
|
}
|
|
|
|
await pool.query('DELETE FROM user_nozzles WHERE id = $1', [nozzleId]);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Nozzle deleted successfully'
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// @route GET /api/nozzles/equipment/:equipmentId/assignments
|
|
// @desc Get nozzle assignments for specific equipment
|
|
// @access Private
|
|
router.get('/equipment/:equipmentId/assignments', 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 ena.*, un.custom_name as nozzle_custom_name, un.quantity as nozzle_total_quantity,
|
|
nt.name as nozzle_type_name, nt.manufacturer, nt.model, nt.orifice_size,
|
|
nt.spray_angle, nt.flow_rate_gpm, nt.droplet_size, nt.spray_pattern
|
|
FROM equipment_nozzle_assignments ena
|
|
JOIN user_nozzles un ON ena.user_nozzle_id = un.id
|
|
LEFT JOIN nozzle_types nt ON un.nozzle_type_id = nt.id
|
|
WHERE ena.user_equipment_id = $1
|
|
ORDER BY ena.position, nt.manufacturer, nt.name`,
|
|
[equipmentId]
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
assignments: result.rows.map(item => ({
|
|
id: item.id,
|
|
userEquipmentId: item.user_equipment_id,
|
|
userNozzleId: item.user_nozzle_id,
|
|
position: item.position,
|
|
quantityAssigned: item.quantity_assigned,
|
|
assignedDate: item.assigned_date,
|
|
nozzleCustomName: item.nozzle_custom_name,
|
|
nozzleTotalQuantity: item.nozzle_total_quantity,
|
|
nozzleTypeName: item.nozzle_type_name,
|
|
manufacturer: item.manufacturer,
|
|
model: item.model,
|
|
orificeSize: item.orifice_size,
|
|
sprayAngle: item.spray_angle,
|
|
flowRateGpm: parseFloat(item.flow_rate_gpm),
|
|
dropletSize: item.droplet_size,
|
|
sprayPattern: item.spray_pattern,
|
|
createdAt: item.created_at
|
|
}))
|
|
}
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// @route POST /api/nozzles/equipment/:equipmentId/assignments
|
|
// @desc Assign nozzle to equipment
|
|
// @access Private
|
|
router.post('/equipment/:equipmentId/assignments', validateParams(idParamSchema), async (req, res, next) => {
|
|
try {
|
|
const equipmentId = req.params.equipmentId;
|
|
const { userNozzleId, position, quantityAssigned } = req.body;
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Verify nozzle belongs to user
|
|
const nozzleCheck = await pool.query(
|
|
'SELECT id, quantity FROM user_nozzles WHERE id = $1 AND user_id = $2',
|
|
[userNozzleId, req.user.id]
|
|
);
|
|
|
|
if (nozzleCheck.rows.length === 0) {
|
|
throw new AppError('Nozzle not found', 404);
|
|
}
|
|
|
|
const nozzle = nozzleCheck.rows[0];
|
|
|
|
// Check if enough nozzles are available
|
|
const assignedCount = await pool.query(
|
|
'SELECT COALESCE(SUM(quantity_assigned), 0) as total_assigned FROM equipment_nozzle_assignments WHERE user_nozzle_id = $1',
|
|
[userNozzleId]
|
|
);
|
|
|
|
const totalAssigned = parseInt(assignedCount.rows[0].total_assigned);
|
|
const requestedQuantity = quantityAssigned || 1;
|
|
|
|
if (totalAssigned + requestedQuantity > nozzle.quantity) {
|
|
throw new AppError(`Not enough nozzles available. Available: ${nozzle.quantity - totalAssigned}, Requested: ${requestedQuantity}`, 400);
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO equipment_nozzle_assignments
|
|
(user_equipment_id, user_nozzle_id, position, quantity_assigned)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING *`,
|
|
[equipmentId, userNozzleId, position || 'center', requestedQuantity]
|
|
);
|
|
|
|
const assignment = result.rows[0];
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Nozzle assigned to equipment successfully',
|
|
data: {
|
|
assignment: {
|
|
id: assignment.id,
|
|
userEquipmentId: assignment.user_equipment_id,
|
|
userNozzleId: assignment.user_nozzle_id,
|
|
position: assignment.position,
|
|
quantityAssigned: assignment.quantity_assigned,
|
|
assignedDate: assignment.assigned_date,
|
|
createdAt: assignment.created_at
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// @route DELETE /api/nozzles/assignments/:assignmentId
|
|
// @desc Remove nozzle assignment from equipment
|
|
// @access Private
|
|
router.delete('/assignments/:assignmentId', validateParams(idParamSchema), async (req, res, next) => {
|
|
try {
|
|
const assignmentId = req.params.assignmentId;
|
|
|
|
// Verify assignment belongs to user's equipment
|
|
const checkResult = await pool.query(
|
|
`SELECT ena.id FROM equipment_nozzle_assignments ena
|
|
JOIN user_equipment ue ON ena.user_equipment_id = ue.id
|
|
WHERE ena.id = $1 AND ue.user_id = $2`,
|
|
[assignmentId, req.user.id]
|
|
);
|
|
|
|
if (checkResult.rows.length === 0) {
|
|
throw new AppError('Assignment not found', 404);
|
|
}
|
|
|
|
await pool.query('DELETE FROM equipment_nozzle_assignments WHERE id = $1', [assignmentId]);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Nozzle assignment removed successfully'
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// 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; |