From 91361560349fe2b272f6671a97c3ca0d2b81449f Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 21 Jun 2026 00:04:55 -0500 Subject: [PATCH 1/4] =?UTF-8?q?Add=20SafetyChecklistHelper=20=E2=80=94=20t?= =?UTF-8?q?rade-specific=20pre-job=20safety=20checks,=20compliance=20gatin?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Helper/SafetyChecklistHelper.php | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 source/packages/plg_system_mokosuitefield/src/Helper/SafetyChecklistHelper.php diff --git a/source/packages/plg_system_mokosuitefield/src/Helper/SafetyChecklistHelper.php b/source/packages/plg_system_mokosuitefield/src/Helper/SafetyChecklistHelper.php new file mode 100644 index 0000000..ae4a76c --- /dev/null +++ b/source/packages/plg_system_mokosuitefield/src/Helper/SafetyChecklistHelper.php @@ -0,0 +1,116 @@ +get(DatabaseInterface::class); + $now = Factory::getDate()->toSql(); + + $items = self::getDefaultItems($trade); + + $checklist = (object) [ + 'wo_id' => $woId, + 'trade' => $trade, + 'status' => 'pending', + 'created' => $now, + 'created_by' => Factory::getApplication()->getIdentity()->id, + ]; + $db->insertObject('#__mokosuitefield_safety_checklists', $checklist, 'id'); + $checklistId = (int) $checklist->id; + + foreach ($items as $i => $item) { + $db->insertObject('#__mokosuitefield_safety_checklist_items', (object) [ + 'checklist_id' => $checklistId, + 'item_text' => $item, + 'checked' => 0, + 'ordering' => $i + 1, + ]); + } + + return $checklistId; + } + + /** + * Complete a checklist item. + */ + public static function checkItem(int $itemId, bool $passed, string $notes = ''): bool + { + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $update = (object) [ + 'id' => $itemId, + 'checked' => 1, + 'passed' => $passed ? 1 : 0, + 'notes' => $notes, + 'checked_at' => Factory::getDate()->toSql(), + 'checked_by' => Factory::getApplication()->getIdentity()->id, + ]; + + return $db->updateObject('#__mokosuitefield_safety_checklist_items', $update, 'id'); + } + + /** + * Get checklist completion status for a work order. + */ + public static function getStatus(int $woId): ?object + { + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('sc.id, sc.status') + ->select('COUNT(sci.id) AS total_items') + ->select('SUM(CASE WHEN sci.checked = 1 THEN 1 ELSE 0 END) AS checked_items') + ->select('SUM(CASE WHEN sci.checked = 1 AND sci.passed = 0 THEN 1 ELSE 0 END) AS failed_items') + ->from($db->quoteName('#__mokosuitefield_safety_checklists', 'sc')) + ->join('LEFT', $db->quoteName('#__mokosuitefield_safety_checklist_items', 'sci') . ' ON sci.checklist_id = sc.id') + ->where('sc.wo_id = ' . (int) $woId) + ->group('sc.id') + ->order('sc.created DESC')); + + $status = $db->loadObject(); + if (!$status) return null; + + $status->complete = (int) $status->checked_items === (int) $status->total_items; + $status->all_passed = (int) $status->failed_items === 0; + $status->safe_to_proceed = $status->complete && $status->all_passed; + + return $status; + } + + /** + * Get default safety items by trade. + */ + private static function getDefaultItems(string $trade): array + { + $common = [ + 'PPE worn (gloves, safety glasses, boots)', + 'Work area inspected for hazards', + 'Tools and equipment in good condition', + 'Fire extinguisher accessible', + 'Emergency exits identified', + ]; + + $tradeSpecific = match ($trade) { + 'electrical' => ['Lockout/tagout verified', 'Voltage tester functional', 'Grounding confirmed', 'Arc flash boundaries marked'], + 'plumbing' => ['Water supply shut off', 'Gas lines identified and marked', 'Asbestos check for older buildings', 'Drain protection in place'], + 'hvac' => ['Refrigerant handling certification verified', 'Electrical isolation confirmed', 'Ductwork supports inspected', 'Ladder/scaffold secured'], + 'general' => ['Work permit obtained if required', 'Material Safety Data Sheets reviewed'], + default => [], + }; + + return array_merge($common, $tradeSpecific); + } +} -- 2.52.0 From bc0fb961b5fde4640638d1affa33342989f9cbfd Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sun, 21 Jun 2026 05:05:09 +0000 Subject: [PATCH 2/4] chore(version): auto-bump patch 01.04.01-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- source/pkg_mokosuitefield.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 75a6963..bd707ae 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 01.00.00 +# VERSION: 01.04.01 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/source/pkg_mokosuitefield.xml b/source/pkg_mokosuitefield.xml index 71c20d4..aac9fbb 100644 --- a/source/pkg_mokosuitefield.xml +++ b/source/pkg_mokosuitefield.xml @@ -2,7 +2,7 @@ Package - MokoSuite Field mokosuitefield - 01.04.00 + 01.04.01 2026-06-12 Moko Consulting hello@mokoconsulting.tech -- 2.52.0 From 4ce4814e509bbe27cb21dab955081e1392914154 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sun, 21 Jun 2026 05:05:20 +0000 Subject: [PATCH 3/4] chore(version): pre-release bump to 01.04.02-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- source/pkg_mokosuitefield.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index bd707ae..8038885 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 01.04.01 +# VERSION: 01.04.02 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/source/pkg_mokosuitefield.xml b/source/pkg_mokosuitefield.xml index aac9fbb..5e8960b 100644 --- a/source/pkg_mokosuitefield.xml +++ b/source/pkg_mokosuitefield.xml @@ -2,7 +2,7 @@ Package - MokoSuite Field mokosuitefield - 01.04.01 + 01.04.02 2026-06-12 Moko Consulting hello@mokoconsulting.tech -- 2.52.0 From eeaef928b547ddcdaa35ea6096ecfaf6f5dc2a83 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 21 Jun 2026 00:24:43 -0500 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20safety=20checklist=20=E2=80=94=20ver?= =?UTF-8?q?ify=20pending+unchecked=20before=20marking,=20auto-complete=20s?= =?UTF-8?q?tatus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Helper/SafetyChecklistHelper.php | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/source/packages/plg_system_mokosuitefield/src/Helper/SafetyChecklistHelper.php b/source/packages/plg_system_mokosuitefield/src/Helper/SafetyChecklistHelper.php index ae4a76c..c789fde 100644 --- a/source/packages/plg_system_mokosuitefield/src/Helper/SafetyChecklistHelper.php +++ b/source/packages/plg_system_mokosuitefield/src/Helper/SafetyChecklistHelper.php @@ -50,6 +50,18 @@ class SafetyChecklistHelper { $db = Factory::getContainer()->get(DatabaseInterface::class); + // Verify item belongs to a pending checklist and is not already checked + $db->setQuery($db->getQuery(true) + ->select('sci.id, sci.checked, sc.status AS checklist_status') + ->from($db->quoteName('#__mokosuitefield_safety_checklist_items', 'sci')) + ->join('INNER', $db->quoteName('#__mokosuitefield_safety_checklists', 'sc') . ' ON sc.id = sci.checklist_id') + ->where('sci.id = ' . (int) $itemId)); + $existing = $db->loadObject(); + + if (!$existing || $existing->checklist_status !== 'pending' || (int) $existing->checked === 1) { + return false; + } + $update = (object) [ 'id' => $itemId, 'checked' => 1, @@ -59,7 +71,26 @@ class SafetyChecklistHelper 'checked_by' => Factory::getApplication()->getIdentity()->id, ]; - return $db->updateObject('#__mokosuitefield_safety_checklist_items', $update, 'id'); + $db->updateObject('#__mokosuitefield_safety_checklist_items', $update, 'id'); + + // Auto-complete checklist if all items are checked + $db->setQuery($db->getQuery(true) + ->select('sc.id, COUNT(sci2.id) AS total, SUM(CASE WHEN sci2.checked = 1 THEN 1 ELSE 0 END) AS done') + ->from($db->quoteName('#__mokosuitefield_safety_checklist_items', 'sci2')) + ->join('INNER', $db->quoteName('#__mokosuitefield_safety_checklists', 'sc') . ' ON sc.id = sci2.checklist_id') + ->where('sci2.id = ' . (int) $itemId) + ->group('sc.id')); + $progress = $db->loadObject(); + + if ($progress && (int) $progress->done === (int) $progress->total) { + $db->setQuery($db->getQuery(true) + ->update('#__mokosuitefield_safety_checklists') + ->set($db->quoteName('status') . ' = ' . $db->quote('completed')) + ->where('id = ' . (int) $progress->id)); + $db->execute(); + } + + return true; } /** -- 2.52.0