linking stuff

This commit is contained in:
Jake Kasper
2025-08-25 08:42:03 -04:00
parent b1dba9e6d7
commit 88996a8f5b
5 changed files with 192 additions and 14 deletions

View File

@@ -7,6 +7,37 @@ const { calculateApplication } = require('../utils/applicationCalculations');
const router = express.Router(); const router = express.Router();
// Helper function to get spreader settings for equipment and product
async function getSpreaderSettingsForEquipment(equipmentId, productId, userProductId, userId) {
// Get spreader settings for the specific product and equipment combination
let query, params;
if (userProductId) {
query = `
SELECT pss.*, ue.custom_name as equipment_name, ue.manufacturer, ue.model as equipment_model
FROM product_spreader_settings pss
JOIN user_equipment ue ON pss.equipment_id = ue.id
WHERE pss.user_product_id = $1 AND pss.equipment_id = $2 AND ue.user_id = $3
ORDER BY pss.created_at DESC
LIMIT 1
`;
params = [userProductId, equipmentId, userId];
} else {
query = `
SELECT pss.*, ue.custom_name as equipment_name, ue.manufacturer, ue.model as equipment_model
FROM product_spreader_settings pss
JOIN user_equipment ue ON pss.equipment_id = ue.id
WHERE pss.product_id = $1 AND pss.equipment_id = $2 AND ue.user_id = $3
ORDER BY pss.created_at DESC
LIMIT 1
`;
params = [productId, equipmentId, userId];
}
const result = await pool.query(query, params);
return result.rows[0] || null;
}
// @route GET /api/applications/plans // @route GET /api/applications/plans
// @desc Get all application plans for current user // @desc Get all application plans for current user
// @access Private // @access Private
@@ -902,4 +933,67 @@ router.get('/stats', async (req, res, next) => {
} }
}); });
// @route GET /api/applications/spreader-settings/:equipmentId/:productId
// @desc Get recommended spreader settings for specific equipment and product combination
// @access Private
router.get('/spreader-settings/:equipmentId/:productId', async (req, res, next) => {
try {
const { equipmentId, productId } = req.params;
const { isUserProduct } = req.query; // Indicates if productId refers to user_products table
// Verify equipment belongs to user
const equipmentCheck = await pool.query(
'SELECT id, custom_name, manufacturer, model 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 equipment = equipmentCheck.rows[0];
// Get spreader settings
const spreaderSetting = await getSpreaderSettingsForEquipment(
parseInt(equipmentId),
isUserProduct === 'true' ? null : parseInt(productId),
isUserProduct === 'true' ? parseInt(productId) : null,
req.user.id
);
if (!spreaderSetting) {
return res.json({
success: true,
data: {
hasSettings: false,
message: 'No spreader settings found for this equipment and product combination',
equipment: {
id: equipment.id,
name: equipment.custom_name || `${equipment.manufacturer} ${equipment.model}`.trim()
}
}
});
}
res.json({
success: true,
data: {
hasSettings: true,
setting: {
id: spreaderSetting.id,
settingValue: spreaderSetting.setting_value,
rateDescription: spreaderSetting.rate_description,
notes: spreaderSetting.notes
},
equipment: {
id: equipment.id,
name: equipment.custom_name || `${equipment.manufacturer} ${equipment.model}`.trim()
}
}
});
} catch (error) {
next(error);
}
});
module.exports = router; module.exports = router;

View File

@@ -182,6 +182,43 @@ router.get('/', async (req, res, next) => {
} }
}); });
// @route GET /api/equipment/spreaders
// @desc Get all spreader equipment for current user (for product spreader settings)
// @access Private
router.get('/spreaders', async (req, res, next) => {
try {
const result = await pool.query(
`SELECT ue.id, ue.custom_name, ue.manufacturer, ue.model, ue.spreader_type,
ue.capacity_lbs, ue.spread_width, ec.name as category_name
FROM user_equipment ue
LEFT JOIN equipment_categories ec ON ue.category_id = ec.id
WHERE ue.user_id = $1
AND ue.is_active = true
AND (ec.name ILIKE '%spreader%' OR ue.spreader_type IS NOT NULL)
ORDER BY ue.custom_name, ue.manufacturer, ue.model`,
[req.user.id]
);
res.json({
success: true,
data: {
spreaders: result.rows.map(item => ({
id: item.id,
name: item.custom_name || `${item.manufacturer} ${item.model}`.trim(),
manufacturer: item.manufacturer,
model: item.model,
spreaderType: item.spreader_type,
capacityLbs: item.capacity_lbs ? parseFloat(item.capacity_lbs) : null,
spreadWidth: item.spread_width ? parseFloat(item.spread_width) : null,
categoryName: item.category_name
}))
}
});
} catch (error) {
next(error);
}
});
// @route GET /api/equipment/:id // @route GET /api/equipment/:id
// @desc Get single equipment item // @desc Get single equipment item
// @access Private // @access Private

View File

@@ -351,6 +351,26 @@ router.post('/user', validateRequest(userProductSchema), async (req, res, next)
if (spreaderSettings && Array.isArray(spreaderSettings) && productType === 'granular') { if (spreaderSettings && Array.isArray(spreaderSettings) && productType === 'granular') {
// Add spreader settings for this user product // Add spreader settings for this user product
for (const setting of spreaderSettings) { for (const setting of spreaderSettings) {
// Check if equipment exists and belongs to user if equipmentId is provided
if (setting.equipmentId) {
const equipmentCheck = await pool.query(
'SELECT id FROM user_equipment WHERE id = $1 AND user_id = $2',
[setting.equipmentId, req.user.id]
);
if (equipmentCheck.rows.length === 0) {
throw new AppError(`Equipment with id ${setting.equipmentId} not found`, 404);
}
await pool.query(
`INSERT INTO product_spreader_settings
(user_product_id, equipment_id, setting_value, rate_description, notes)
VALUES ($1, $2, $3, $4, $5)`,
[userProduct.id, setting.equipmentId, setting.settingValue,
setting.rateDescription, setting.notes]
);
} else {
// Fall back to legacy brand/model approach
await pool.query( await pool.query(
`INSERT INTO product_spreader_settings `INSERT INTO product_spreader_settings
(user_product_id, spreader_brand, spreader_model, setting_value, rate_description, notes) (user_product_id, spreader_brand, spreader_model, setting_value, rate_description, notes)
@@ -360,6 +380,7 @@ router.post('/user', validateRequest(userProductSchema), async (req, res, next)
); );
} }
} }
}
res.status(201).json({ res.status(201).json({
success: true, success: true,
@@ -412,17 +433,27 @@ router.get('/user/:id', validateParams(idParamSchema), async (req, res, next) =>
const userProduct = result.rows[0]; const userProduct = result.rows[0];
// Get spreader settings for this user product // Get spreader settings for this user product with equipment details
let spreaderSettings = []; let spreaderSettings = [];
const settingsResult = await pool.query( const settingsResult = await pool.query(
`SELECT * FROM product_spreader_settings `SELECT pss.*, ue.custom_name as equipment_name, ue.manufacturer, ue.model as equipment_model,
WHERE user_product_id = $1 ue.spreader_type, ue.capacity_lbs
ORDER BY spreader_brand, spreader_model NULLS LAST, setting_value`, FROM product_spreader_settings pss
LEFT JOIN user_equipment ue ON pss.equipment_id = ue.id
WHERE pss.user_product_id = $1
ORDER BY ue.custom_name NULLS LAST, pss.spreader_brand, pss.spreader_model NULLS LAST, pss.setting_value`,
[userProductId] [userProductId]
); );
spreaderSettings = settingsResult.rows.map(row => ({ spreaderSettings = settingsResult.rows.map(row => ({
id: row.id, id: row.id,
equipmentId: row.equipment_id,
equipmentName: row.equipment_name,
equipmentManufacturer: row.manufacturer,
equipmentModel: row.equipment_model,
equipmentType: row.spreader_type,
equipmentCapacity: row.capacity_lbs ? parseFloat(row.capacity_lbs) : null,
// Legacy fields
spreaderBrand: row.spreader_brand, spreaderBrand: row.spreader_brand,
spreaderModel: row.spreader_model, spreaderModel: row.spreader_model,
settingValue: row.setting_value, settingValue: row.setting_value,

View File

@@ -91,8 +91,10 @@ const userProductSchema = Joi.object({
// Spreader settings for granular products // Spreader settings for granular products
spreaderSettings: Joi.array().items( spreaderSettings: Joi.array().items(
Joi.object({ Joi.object({
id: Joi.number().optional(), // For frontend temporary IDs id: Joi.number().optional(), // For existing settings
spreaderBrand: Joi.string().max(100).required(), equipmentId: Joi.number().integer().positive().optional(), // Link to user_equipment
// Legacy fields for backward compatibility
spreaderBrand: Joi.string().max(100).optional(),
spreaderModel: Joi.alternatives().try( spreaderModel: Joi.alternatives().try(
Joi.string().max(100).allow(''), Joi.string().max(100).allow(''),
Joi.allow(null) Joi.allow(null)
@@ -106,7 +108,7 @@ const userProductSchema = Joi.object({
Joi.string().allow(''), Joi.string().allow(''),
Joi.allow(null) Joi.allow(null)
).optional() ).optional()
}) }).or('equipmentId', 'spreaderBrand') // Must have either equipment reference or brand
).optional() ).optional()
}); });

View File

@@ -0,0 +1,14 @@
-- Link spreader settings to actual user equipment instead of storing text
-- This allows better integration and recommendations
-- Add equipment reference to product_spreader_settings
ALTER TABLE product_spreader_settings
ADD COLUMN IF NOT EXISTS equipment_id INTEGER REFERENCES user_equipment(id) ON DELETE CASCADE;
-- Create index for the new relationship
CREATE INDEX IF NOT EXISTS idx_product_spreader_settings_equipment ON product_spreader_settings(equipment_id);
-- We'll keep the old columns for backward compatibility during transition
-- They can be removed later once all data is migrated
SELECT 'Added equipment reference to spreader settings!' as migration_status;