diff --git a/source/packages/com_mokosuitefield/admin/src/Model/WorkOrdersModel.php b/source/packages/com_mokosuitefield/admin/src/Model/WorkOrdersModel.php index 6d34474..af03434 100644 --- a/source/packages/com_mokosuitefield/admin/src/Model/WorkOrdersModel.php +++ b/source/packages/com_mokosuitefield/admin/src/Model/WorkOrdersModel.php @@ -38,13 +38,14 @@ class WorkOrdersModel extends BaseDatabaseModel { $db = $this->getDatabase(); $db->setQuery($db->getQuery(true) - ->select('wo.*, cd.name AS customer_name, loc.*, t_cd.name AS tech_name') + ->select('wo.*, cd.name AS customer_name, t_cd.name AS tech_name') + ->select('loc.address, loc.city, loc.state, loc.zip, loc.latitude, loc.longitude, loc.name AS location_name') ->from($db->quoteName('#__mokosuitefield_work_orders', 'wo')) ->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = wo.contact_id') ->join('LEFT', $db->quoteName('#__mokosuitefield_locations', 'loc') . ' ON loc.id = wo.location_id') ->join('LEFT', $db->quoteName('#__mokosuitefield_technicians', 't') . ' ON t.id = wo.technician_id') ->join('LEFT', $db->quoteName('#__contact_details', 't_cd') . ' ON t_cd.id = t.contact_id') - ->where('wo.id = ' . $id)); + ->where('wo.id = ' . (int) $id)); return $db->loadObject(); } diff --git a/source/packages/com_mokosuitefield/site/src/View/EstimateView/HtmlView.php b/source/packages/com_mokosuitefield/site/src/View/EstimateView/HtmlView.php index 3cd10af..6c6f8eb 100644 --- a/source/packages/com_mokosuitefield/site/src/View/EstimateView/HtmlView.php +++ b/source/packages/com_mokosuitefield/site/src/View/EstimateView/HtmlView.php @@ -55,8 +55,8 @@ class HtmlView extends BaseHtmlView ->order('ordering ASC')); $this->lineItems = $db->loadObjectList() ?: []; - // Handle approval/rejection - if ($input->getMethod() === 'POST') { + // Handle approval/rejection (CSRF check not required — token-based public page) + if ($input->getMethod() === 'POST' && \Joomla\CMS\Session\Session::checkToken()) { $action = $input->getString('action', ''); if ($action === 'approve' && $this->estimate->status === 'sent') { @@ -71,7 +71,7 @@ class HtmlView extends BaseHtmlView $this->actioned = true; $this->actionResult = 'approved'; $this->estimate->status = 'approved'; - } elseif ($action === 'reject') { + } elseif ($action === 'reject' && $this->estimate->status === 'sent') { $db->setQuery($db->getQuery(true) ->update('#__mokosuitefield_estimates') ->set($db->quoteName('status') . ' = ' . $db->quote('rejected')) diff --git a/source/packages/com_mokosuitefield/site/tmpl/estimateview/default.php b/source/packages/com_mokosuitefield/site/tmpl/estimateview/default.php index 06b27d0..50fa071 100644 --- a/source/packages/com_mokosuitefield/site/tmpl/estimateview/default.php +++ b/source/packages/com_mokosuitefield/site/tmpl/estimateview/default.php @@ -61,6 +61,7 @@ $total = $subtotal + $tax;
+
@@ -76,6 +77,7 @@ $total = $subtotal + $tax;
+
diff --git a/source/packages/plg_system_mokosuitefield/src/Helper/InvoiceHelper.php b/source/packages/plg_system_mokosuitefield/src/Helper/InvoiceHelper.php index 5d976ce..83e51b7 100644 --- a/source/packages/plg_system_mokosuitefield/src/Helper/InvoiceHelper.php +++ b/source/packages/plg_system_mokosuitefield/src/Helper/InvoiceHelper.php @@ -18,6 +18,7 @@ class InvoiceHelper { $db = Factory::getContainer()->get(DatabaseInterface::class); $now = Factory::getDate()->toSql(); + $woId = (int) $woId; // Load work order $db->setQuery($db->getQuery(true) @@ -29,6 +30,8 @@ class InvoiceHelper if (!$wo) throw new \RuntimeException('Work order not found: ' . $woId); + $db->transactionStart(); + $params = Factory::getApplication()->getParams('com_mokosuitefield'); $laborRate = (float) $params->get('default_labor_rate', 85.00); @@ -113,6 +116,8 @@ class InvoiceHelper ->where('id = ' . $woId)); $db->execute(); + $db->transactionCommit(); + return $invoiceId; } diff --git a/source/packages/plg_system_mokosuitefield/src/Helper/RouteHelper.php b/source/packages/plg_system_mokosuitefield/src/Helper/RouteHelper.php index ba4d113..701348c 100644 --- a/source/packages/plg_system_mokosuitefield/src/Helper/RouteHelper.php +++ b/source/packages/plg_system_mokosuitefield/src/Helper/RouteHelper.php @@ -11,6 +11,8 @@ use Joomla\Database\DatabaseInterface; */ class RouteHelper { + private const AVG_SPEED_MPH = 30; + /** * Get today's route for a technician (work orders sorted by scheduled time). */ @@ -185,14 +187,14 @@ class RouteHelper 'wo_id' => $stop->id, 'location' => $stop->location_name ?? $stop->address, 'distance' => round($dist, 1), - 'drive_min'=> round($dist / 0.5, 0), // ~30 mph avg in service areas + 'drive_min'=> round($dist / self::AVG_SPEED_MPH * 60, 0), ]; $prevLat = $lat ?: $prevLat; $prevLng = $lng ?: $prevLng; } - $totalDriveMin = $totalDistance > 0 ? round($totalDistance / 0.5) : 0; + $totalDriveMin = $totalDistance > 0 ? round($totalDistance / self::AVG_SPEED_MPH * 60) : 0; return (object) [ 'stop_count' => count($stops),