mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-10-02 23:55:19 -04:00
feat(items): optimize search, attribute filtering, and sort for items view (#4652) BREAKING CHANGE: none ## Search & Query Optimization - Split item search into two-phase query: Phase A resolves qualifying IDs, Phase B joins scoped display tables for better performance and readability - Sanitize `definition_ids` via `array_map` to prevent injection vulnerabilities - Introduce subquery for SUM aggregation to prevent over-counting across joins - Add validation requiring both start and end dates before applying date range filter ## Attribute Search (fixes #2919, #2722) - Add `SHOW_IN_SEARCH` flag (value 8) to Attribute model to separate searchability from table visibility - Add `parse_attribute_search()` to parse syntax like `color:blue AND size:large` - Add `applyNamedAttributeSearch()` supporting decimal and date types with locale-aware parsing - Support AND/OR logic for multi-attribute queries ## Sorting - Add `get_attribute_sort_definition_id()` to detect attribute column sorting - Join attribute tables dynamically when sorting by attribute columns - Use `MAX()` for consistent results when sorting by attribute values - Replace static sort column list with dynamic headers via `itemSortColumns()` - Add `sanitizeSortColumnAttribute()` to validate attribute definition IDs as sort columns ## Tax & Data Row - Streamline tax computation in `getItemDataRow()` ## Low Inventory Filter - Require valid `stock_location_id` before applying low inventory filter - Add conditional logic to sort by sum of quantities across all locations when `stock_location_id` is invalid ## Localization - Add `show_in_search` / `show_in_search_visibility` strings to all language files - Translated: de-DE, es-ES, fr, it; English placeholder for remaining locales - Unify single-quote style across all attribute language files ## Refactoring & Style - Adopt camelCase naming throughout (variables, helpers, methods) - Replace `sanitizeSortColumnAttribute` with reusable `sanitizeSortColumn` from `Secure_Controller` - Simplify column key extraction using `array_key_first` - Apply PSR-12 formatting ## Tests - Add tests for tax computation, quantity aggregation (single- and multi-location), named attribute search, free-text parsing, and date/decimal type handling - Add `ensureStockLocation` helper to auto-create missing stock locations in tests - Refactor tests to handle config cache issues Co-authored-by: Ollama <ollama@steganos.dev>
80 lines
2.4 KiB
PHP
80 lines
2.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Models;
|
|
|
|
use CodeIgniter\Test\CIUnitTestCase;
|
|
use CodeIgniter\Test\DatabaseTestTrait;
|
|
use App\Models\Item_taxes;
|
|
use Config\Database;
|
|
use Tests\Support\ItemSearchFixtureTrait;
|
|
|
|
class ItemTaxesMultipleTest extends CIUnitTestCase
|
|
{
|
|
use DatabaseTestTrait;
|
|
use ItemSearchFixtureTrait;
|
|
|
|
protected $migrate = true;
|
|
protected $migrateOnce = true;
|
|
protected $seed = '';
|
|
protected $seedOnce = true;
|
|
protected $refresh = true;
|
|
protected $namespace = null;
|
|
|
|
protected $item_taxes;
|
|
|
|
public static function setUpBeforeClass(): void
|
|
{
|
|
$seeder = Database::seeder('tests');
|
|
$seeder->call('TestDatabaseBootstrapSeeder');
|
|
}
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->item_taxes = model(Item_taxes::class);
|
|
}
|
|
|
|
public function testGetInfoMultipleReturnsTaxesGroupedByItemId(): void
|
|
{
|
|
$itemOneId = $this->createSearchableItem();
|
|
$itemTwoId = $this->createSearchableItem();
|
|
|
|
$itemOneTaxes = [['name' => 'VAT', 'percent' => 20]];
|
|
$itemTwoTaxes = [['name' => 'GST', 'percent' => 10]];
|
|
|
|
$this->item_taxes->save_value($itemOneTaxes, $itemOneId);
|
|
$this->item_taxes->save_value($itemTwoTaxes, $itemTwoId);
|
|
|
|
$result = $this->item_taxes->getInfoMultiple([$itemOneId, $itemTwoId]);
|
|
|
|
$this->assertArrayHasKey($itemOneId, $result);
|
|
$this->assertArrayHasKey($itemTwoId, $result);
|
|
$this->assertCount(1, $result[$itemOneId]);
|
|
$this->assertCount(1, $result[$itemTwoId]);
|
|
$this->assertEquals('VAT', $result[$itemOneId][0]['name']);
|
|
$this->assertEquals('GST', $result[$itemTwoId][0]['name']);
|
|
}
|
|
|
|
public function testGetInfoMultipleItemWithNoTaxesOmittedFromResult(): void
|
|
{
|
|
$itemWithTaxesId = $this->createSearchableItem();
|
|
$itemWithoutTaxesId = $this->createSearchableItem();
|
|
|
|
$taxes = [['name' => 'VAT', 'percent' => 20]];
|
|
$this->item_taxes->save_value($taxes, $itemWithTaxesId);
|
|
|
|
$result = $this->item_taxes->getInfoMultiple([$itemWithTaxesId, $itemWithoutTaxesId]);
|
|
|
|
$this->assertArrayHasKey($itemWithTaxesId, $result);
|
|
$this->assertArrayNotHasKey($itemWithoutTaxesId, $result);
|
|
}
|
|
|
|
public function testGetInfoMultipleEmptyItemIdsReturnsEmptyArray(): void
|
|
{
|
|
$result = $this->item_taxes->getInfoMultiple([]);
|
|
|
|
$this->assertSame([], $result);
|
|
}
|
|
}
|