calcs
This commit is contained in:
@@ -42,7 +42,10 @@ router.get('/plans', async (req, res, next) => {
|
||||
`SELECT ap.*, ls.name as section_name, ls.area as section_area,
|
||||
p.name as property_name, p.address as property_address,
|
||||
ue.custom_name as equipment_name, et.name as equipment_type,
|
||||
COUNT(app.id) as product_count
|
||||
COUNT(app.id) as product_count,
|
||||
SUM(app.calculated_product_amount) as total_product_amount,
|
||||
SUM(app.calculated_water_amount) as total_water_amount,
|
||||
AVG(app.target_speed_mph) as avg_speed_mph
|
||||
FROM application_plans ap
|
||||
JOIN lawn_sections ls ON ap.lawn_section_id = ls.id
|
||||
JOIN properties p ON ls.property_id = p.id
|
||||
@@ -69,6 +72,9 @@ router.get('/plans', async (req, res, next) => {
|
||||
propertyAddress: plan.property_address,
|
||||
equipmentName: plan.equipment_name || plan.equipment_type,
|
||||
productCount: parseInt(plan.product_count),
|
||||
totalProductAmount: parseFloat(plan.total_product_amount || 0),
|
||||
totalWaterAmount: parseFloat(plan.total_water_amount || 0),
|
||||
avgSpeedMph: parseFloat(plan.avg_speed_mph || 0),
|
||||
createdAt: plan.created_at,
|
||||
updatedAt: plan.updated_at
|
||||
}))
|
||||
@@ -207,9 +213,13 @@ router.post('/plans', validateRequest(applicationPlanSchema), async (req, res, n
|
||||
|
||||
const section = sectionCheck.rows[0];
|
||||
|
||||
// Verify equipment belongs to user
|
||||
// Verify equipment belongs to user and get equipment details with category
|
||||
const equipmentCheck = await client.query(
|
||||
'SELECT id, tank_size, pump_gpm, nozzle_gpm, nozzle_count FROM user_equipment WHERE id = $1 AND user_id = $2',
|
||||
`SELECT ue.*, et.name as type_name, ec.name as category_name
|
||||
FROM user_equipment ue
|
||||
LEFT JOIN equipment_types et ON ue.equipment_type_id = et.id
|
||||
LEFT JOIN equipment_categories ec ON COALESCE(et.category_id, ue.category_id) = ec.id
|
||||
WHERE ue.id = $1 AND ue.user_id = $2`,
|
||||
[equipmentId, req.user.id]
|
||||
);
|
||||
|
||||
@@ -217,7 +227,20 @@ router.post('/plans', validateRequest(applicationPlanSchema), async (req, res, n
|
||||
throw new AppError('Equipment not found', 404);
|
||||
}
|
||||
|
||||
const equipment = equipmentCheck.rows[0];
|
||||
const equipmentData = equipmentCheck.rows[0];
|
||||
|
||||
// Get nozzle data if provided
|
||||
let nozzleData = null;
|
||||
if (nozzleId) {
|
||||
const nozzleCheck = await client.query(
|
||||
'SELECT * FROM user_equipment WHERE id = $1 AND user_id = $2',
|
||||
[nozzleId, req.user.id]
|
||||
);
|
||||
|
||||
if (nozzleCheck.rows.length > 0) {
|
||||
nozzleData = nozzleCheck.rows[0];
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Creating plan with data:', {
|
||||
userId: req.user.id,
|
||||
@@ -245,14 +268,38 @@ router.post('/plans', validateRequest(applicationPlanSchema), async (req, res, n
|
||||
// Use passed area or get from database
|
||||
const sectionArea = areaSquareFeet || parseFloat(section.area);
|
||||
|
||||
console.log('Calculation inputs:', {
|
||||
areaSquareFeet: sectionArea,
|
||||
rateAmount: parseFloat(rateAmount),
|
||||
rateUnit,
|
||||
applicationType,
|
||||
equipmentData,
|
||||
nozzleData
|
||||
});
|
||||
|
||||
// Prepare equipment object for calculations
|
||||
const equipmentForCalc = {
|
||||
categoryName: equipmentData.category_name,
|
||||
tankSizeGallons: equipmentData.tank_size_gallons,
|
||||
sprayWidthFeet: equipmentData.spray_width_feet,
|
||||
capacityLbs: equipmentData.capacity_lbs,
|
||||
spreadWidth: equipmentData.spread_width
|
||||
};
|
||||
|
||||
// Prepare nozzle object for calculations
|
||||
const nozzleForCalc = nozzleData ? {
|
||||
flowRateGpm: nozzleData.flow_rate_gpm,
|
||||
sprayAngle: nozzleData.spray_angle
|
||||
} : null;
|
||||
|
||||
// Perform advanced calculations using the calculation engine
|
||||
const calculations = calculateApplication({
|
||||
areaSquareFeet: sectionArea,
|
||||
rateAmount: parseFloat(rateAmount),
|
||||
rateUnit,
|
||||
applicationType,
|
||||
equipment,
|
||||
nozzle
|
||||
equipment: equipmentForCalc,
|
||||
nozzle: nozzleForCalc
|
||||
});
|
||||
|
||||
console.log('Plan creation calculations:', calculations);
|
||||
@@ -282,6 +329,16 @@ router.post('/plans', validateRequest(applicationPlanSchema), async (req, res, n
|
||||
|
||||
await client.query('COMMIT');
|
||||
|
||||
// Get the created plan with calculations for response
|
||||
const createdPlanResult = await client.query(
|
||||
`SELECT ap.*, app.calculated_product_amount, app.calculated_water_amount, app.target_speed_mph,
|
||||
app.rate_amount, app.rate_unit
|
||||
FROM application_plans ap
|
||||
LEFT JOIN application_plan_products app ON ap.id = app.plan_id
|
||||
WHERE ap.id = $1`,
|
||||
[plan.id]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'Application plan created successfully',
|
||||
@@ -291,7 +348,14 @@ router.post('/plans', validateRequest(applicationPlanSchema), async (req, res, n
|
||||
status: plan.status,
|
||||
plannedDate: plan.planned_date,
|
||||
notes: plan.notes,
|
||||
createdAt: plan.created_at
|
||||
createdAt: plan.created_at,
|
||||
calculations: createdPlanResult.rows.map(row => ({
|
||||
productAmount: row.calculated_product_amount,
|
||||
waterAmount: row.calculated_water_amount,
|
||||
targetSpeed: row.target_speed_mph,
|
||||
rateAmount: row.rate_amount,
|
||||
rateUnit: row.rate_unit
|
||||
}))
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -308,6 +372,178 @@ router.post('/plans', validateRequest(applicationPlanSchema), async (req, res, n
|
||||
}
|
||||
});
|
||||
|
||||
// @route PUT /api/applications/plans/:id
|
||||
// @desc Update application plan
|
||||
// @access Private
|
||||
router.put('/plans/:id', validateParams(idParamSchema), validateRequest(applicationPlanSchema), async (req, res, next) => {
|
||||
try {
|
||||
const planId = req.params.id;
|
||||
const {
|
||||
lawnSectionId,
|
||||
equipmentId,
|
||||
nozzleId,
|
||||
plannedDate,
|
||||
notes,
|
||||
products,
|
||||
areaSquareFeet,
|
||||
equipment,
|
||||
nozzle
|
||||
} = req.body;
|
||||
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Check if plan belongs to user
|
||||
const planCheck = await client.query(
|
||||
'SELECT id FROM application_plans WHERE id = $1 AND user_id = $2',
|
||||
[planId, req.user.id]
|
||||
);
|
||||
|
||||
if (planCheck.rows.length === 0) {
|
||||
throw new AppError('Application plan not found', 404);
|
||||
}
|
||||
|
||||
// Verify lawn section belongs to user
|
||||
const sectionCheck = await client.query(
|
||||
`SELECT ls.id, ls.area, p.user_id
|
||||
FROM lawn_sections ls
|
||||
JOIN properties p ON ls.property_id = p.id
|
||||
WHERE ls.id = $1 AND p.user_id = $2`,
|
||||
[lawnSectionId, req.user.id]
|
||||
);
|
||||
|
||||
if (sectionCheck.rows.length === 0) {
|
||||
throw new AppError('Lawn section not found', 404);
|
||||
}
|
||||
|
||||
const section = sectionCheck.rows[0];
|
||||
|
||||
// Verify equipment belongs to user and get equipment details
|
||||
const equipmentCheck = await client.query(
|
||||
`SELECT ue.*, et.name as type_name, ec.name as category_name
|
||||
FROM user_equipment ue
|
||||
LEFT JOIN equipment_types et ON ue.equipment_type_id = et.id
|
||||
LEFT JOIN equipment_categories ec ON COALESCE(et.category_id, ue.category_id) = ec.id
|
||||
WHERE ue.id = $1 AND ue.user_id = $2`,
|
||||
[equipmentId, req.user.id]
|
||||
);
|
||||
|
||||
if (equipmentCheck.rows.length === 0) {
|
||||
throw new AppError('Equipment not found', 404);
|
||||
}
|
||||
|
||||
const equipmentData = equipmentCheck.rows[0];
|
||||
|
||||
// Get nozzle data if provided
|
||||
let nozzleData = null;
|
||||
if (nozzleId) {
|
||||
const nozzleCheck = await client.query(
|
||||
'SELECT * FROM user_equipment WHERE id = $1 AND user_id = $2',
|
||||
[nozzleId, req.user.id]
|
||||
);
|
||||
|
||||
if (nozzleCheck.rows.length > 0) {
|
||||
nozzleData = nozzleCheck.rows[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Update application plan
|
||||
const updateResult = await client.query(
|
||||
`UPDATE application_plans
|
||||
SET lawn_section_id = $1, equipment_id = $2, nozzle_id = $3,
|
||||
planned_date = $4, notes = $5, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $6
|
||||
RETURNING *`,
|
||||
[lawnSectionId, equipmentId, nozzleId, plannedDate, notes, planId]
|
||||
);
|
||||
|
||||
const plan = updateResult.rows[0];
|
||||
|
||||
// Delete existing products
|
||||
await client.query('DELETE FROM application_plan_products WHERE plan_id = $1', [planId]);
|
||||
|
||||
// Add updated products with recalculation
|
||||
for (const product of products) {
|
||||
const { productId, userProductId, rateAmount, rateUnit, applicationType } = product;
|
||||
|
||||
const sectionArea = areaSquareFeet || parseFloat(section.area);
|
||||
|
||||
// Prepare equipment object for calculations
|
||||
const equipmentForCalc = {
|
||||
categoryName: equipmentData.category_name,
|
||||
tankSizeGallons: equipmentData.tank_size_gallons,
|
||||
sprayWidthFeet: equipmentData.spray_width_feet,
|
||||
capacityLbs: equipmentData.capacity_lbs,
|
||||
spreadWidth: equipmentData.spread_width
|
||||
};
|
||||
|
||||
// Prepare nozzle object for calculations
|
||||
const nozzleForCalc = nozzleData ? {
|
||||
flowRateGpm: nozzleData.flow_rate_gpm,
|
||||
sprayAngle: nozzleData.spray_angle
|
||||
} : null;
|
||||
|
||||
// Perform recalculation
|
||||
const calculations = calculateApplication({
|
||||
areaSquareFeet: sectionArea,
|
||||
rateAmount: parseFloat(rateAmount),
|
||||
rateUnit,
|
||||
applicationType,
|
||||
equipment: equipmentForCalc,
|
||||
nozzle: nozzleForCalc
|
||||
});
|
||||
|
||||
// Extract calculated values
|
||||
let calculatedProductAmount = 0;
|
||||
let calculatedWaterAmount = 0;
|
||||
let targetSpeed = calculations.applicationSpeedMph || 3;
|
||||
|
||||
if (calculations.type === 'liquid') {
|
||||
calculatedProductAmount = calculations.productAmountOunces || 0;
|
||||
calculatedWaterAmount = calculations.waterAmountGallons || 0;
|
||||
} else if (calculations.type === 'granular') {
|
||||
calculatedProductAmount = calculations.productAmountPounds || 0;
|
||||
calculatedWaterAmount = 0;
|
||||
}
|
||||
|
||||
await client.query(
|
||||
`INSERT INTO application_plan_products
|
||||
(plan_id, product_id, user_product_id, rate_amount, rate_unit,
|
||||
calculated_product_amount, calculated_water_amount, target_speed_mph)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
||||
[plan.id, productId, userProductId, rateAmount, rateUnit,
|
||||
calculatedProductAmount, calculatedWaterAmount, targetSpeed]
|
||||
);
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Application plan updated successfully',
|
||||
data: {
|
||||
plan: {
|
||||
id: plan.id,
|
||||
status: plan.status,
|
||||
plannedDate: plan.planned_date,
|
||||
notes: plan.notes,
|
||||
updatedAt: plan.updated_at
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// @route PUT /api/applications/plans/:id/status
|
||||
// @desc Update application plan status
|
||||
// @access Private
|
||||
|
||||
@@ -156,9 +156,22 @@ const Applications = () => {
|
||||
<p className="text-sm text-gray-600 mb-1">
|
||||
Equipment: {application.equipmentName}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">
|
||||
<p className="text-sm text-gray-600 mb-1">
|
||||
Products: {application.productCount}
|
||||
</p>
|
||||
{/* Display calculated amounts */}
|
||||
{application.totalProductAmount > 0 && (
|
||||
<div className="text-sm text-green-600 mt-2 space-y-1">
|
||||
<p className="font-medium">Calculated Requirements:</p>
|
||||
<p>• Product: {application.totalProductAmount.toFixed(2)} {application.totalWaterAmount > 0 ? 'oz' : 'lbs'}</p>
|
||||
{application.totalWaterAmount > 0 && (
|
||||
<p>• Water: {application.totalWaterAmount.toFixed(2)} gallons</p>
|
||||
)}
|
||||
{application.avgSpeedMph > 0 && (
|
||||
<p>• Target Speed: {application.avgSpeedMph.toFixed(1)} mph</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{application.notes && (
|
||||
<p className="text-sm text-gray-500 mt-2 italic">
|
||||
"{application.notes}"
|
||||
|
||||
@@ -151,6 +151,7 @@ export const applicationsAPI = {
|
||||
getPlans: (params) => apiClient.get('/applications/plans', { params }),
|
||||
getPlan: (id) => apiClient.get(`/applications/plans/${id}`),
|
||||
createPlan: (planData) => apiClient.post('/applications/plans', planData),
|
||||
updatePlan: (id, planData) => apiClient.put(`/applications/plans/${id}`, planData),
|
||||
updatePlanStatus: (id, status) => apiClient.put(`/applications/plans/${id}/status`, { status }),
|
||||
|
||||
// Logs
|
||||
|
||||
Reference in New Issue
Block a user