From 8d830a67545051196fc3e75dbffd71bd769885f7 Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Sun, 16 Aug 2026 15:29:51 +0600 Subject: [PATCH] feat(grid): filter category products by relation columns Apply filter_{field} LIKE on joined relation display fields and send the same prefix from the manager grid so UI filters match option-column behaviour. --- .../Category/CategoryProductsListService.php | 7 +++ .../CategoryProductsRelationFilterTest.php | 46 +++++++++++++++++++ .../tests/RelationColumnSpecTest.php | 5 ++ .../src/components/CategoryProductsGrid.vue | 4 +- 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 core/components/minishop3/tests/CategoryProductsRelationFilterTest.php diff --git a/core/components/minishop3/src/Services/Category/CategoryProductsListService.php b/core/components/minishop3/src/Services/Category/CategoryProductsListService.php index ddc91b28..17bd05e1 100644 --- a/core/components/minishop3/src/Services/Category/CategoryProductsListService.php +++ b/core/components/minishop3/src/Services/Category/CategoryProductsListService.php @@ -226,6 +226,13 @@ private function buildProductListQuery( } } + foreach ($relationSpecs as $spec) { + $paramKey = 'filter_' . $spec->fieldName; + if (isset($params[$paramKey]) && $params[$paramKey] !== '') { + $c->where([$spec->sortExpression() . ':LIKE' => "%{$params[$paramKey]}%"]); + } + } + if (!isset($params['deleted']) || $params['deleted'] === '') { $c->where(['msProduct.deleted' => 0]); } diff --git a/core/components/minishop3/tests/CategoryProductsRelationFilterTest.php b/core/components/minishop3/tests/CategoryProductsRelationFilterTest.php new file mode 100644 index 00000000..ac48c5e6 --- /dev/null +++ b/core/components/minishop3/tests/CategoryProductsRelationFilterTest.php @@ -0,0 +1,46 @@ +selectExpression(), 'vendor select expression' ); +$assertSame( + '`rel_msVendor_vendor_id`.address', + $spec->sortExpression(), + 'vendor sort/filter expression' +); $assertNull( RelationColumnSpec::fromGroupField($group, [ diff --git a/vueManager/src/components/CategoryProductsGrid.vue b/vueManager/src/components/CategoryProductsGrid.vue index 8e554172..92f11a4c 100644 --- a/vueManager/src/components/CategoryProductsGrid.vue +++ b/vueManager/src/components/CategoryProductsGrid.vue @@ -143,7 +143,7 @@ async function loadProducts() { nested: nested.value ? 1 : 0, } - // Apply filter values. Option-type columns are JOIN-ed at runtime — backend + // Apply filter values. Option/relation columns are JOIN-ed at runtime — backend // reads their filters as `filter_{fieldName}` (see CategoryProductsListService). // Builtin product/data filters keep the original direct-param contract. Object.keys(filterValues.value).forEach(key => { @@ -152,7 +152,7 @@ async function loadProducts() { return } const col = columns.value.find(c => c.name === key) - if (col && col.type === 'option') { + if (col && (col.type === 'option' || col.type === 'relation')) { params[`filter_${key}`] = value } else { params[key] = value