From e602eddb47e334089ceeb2e4f441123108b126dc Mon Sep 17 00:00:00 2001 From: jekkos Date: Mon, 20 Apr 2026 06:22:42 +0000 Subject: [PATCH] fix: Scope orWhere clauses in Item::exists() and Item::get_item_id() (#4520) In PR #4250 (commit 29c3c55), orWhere was added to match items by either item_id or item_number, but the OR condition was not wrapped in groupStart()/groupEnd(). This causes: 1. Wrong SQL semantics: generates WHERE item_id = ? OR item_number = ? AND deleted = 0 instead of WHERE (item_id = ? OR item_number = ?) AND deleted = 0 Due to AND binding tighter than OR, the deleted filter only applies to the item_number branch, allowing deleted items to match via item_id. 2. Performance: the unscoped OR causes MySQL to bypass the item_id primary key index and fall back to full table scans when item_number is a string column compared against a numeric parameter. Both exists() and get_item_id() are fixed by wrapping the OR conditions in groupStart()/groupEnd() for proper parenthesization. Co-authored-by: Ollama --- app/Models/Item.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Models/Item.php b/app/Models/Item.php index 3759810a0..7ca16379d 100644 --- a/app/Models/Item.php +++ b/app/Models/Item.php @@ -65,8 +65,10 @@ class Item extends Model public function exists(string $item_id, bool $ignore_deleted = false, bool $deleted = false): bool { $builder = $this->db->table('items'); + $builder->groupStart(); $builder->where('item_id', $item_id); $builder->orWhere('item_number', $item_id); + $builder->groupEnd(); if (!$ignore_deleted) { $builder->where('deleted', $deleted); @@ -390,8 +392,10 @@ class Item extends Model { $builder = $this->db->table('items'); $builder->join('suppliers', 'suppliers.person_id = items.supplier_id', 'left'); + $builder->groupStart(); $builder->where('item_number', $item_number); $builder->orWhere('item_id', $item_number); + $builder->groupEnd(); if (!$ignore_deleted) { $builder->where('items.deleted', $deleted);