From ce2bdc43a8536dcc2957343ec5739f63ecb04238 Mon Sep 17 00:00:00 2001 From: Ian Roddis <31021769+iroddis@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:05:01 -0300 Subject: [PATCH] Making text search apply to both description and sku in imported parts and augmented parts --- Makefile | 2 +- app/crud/augmented_part.py | 30 ++++------------ app/crud/imported_part.py | 12 +++++-- app/services/scope_guide.py | 1 - .../admin/_augmented_parts_table.html | 2 +- .../admin/_imported_parts_table.html | 2 +- tests/test_admin_sorting.py | 34 +++++++++++++++++++ 7 files changed, 53 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index 0fa08dc..c0a4eec 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ seed: uv run alembic upgrade head uv run python -m scripts.seed -dev: +dev: migrate uv run uvicorn app.main:app --reload open http://localhost:8000 diff --git a/app/crud/augmented_part.py b/app/crud/augmented_part.py index 87dda10..5e72984 100644 --- a/app/crud/augmented_part.py +++ b/app/crud/augmented_part.py @@ -57,14 +57,9 @@ async def get_by_sku(db: AsyncSession, sku: str) -> AugmentedPart | None: return result.scalars().first() -async def list_all( - db: AsyncSession, skip: int = 0, limit: int = 100 -) -> list[AugmentedPart]: +async def list_all(db: AsyncSession, skip: int = 0, limit: int = 100) -> list[AugmentedPart]: result = await db.execute( - select(AugmentedPart) - .order_by(AugmentedPart.description) - .offset(skip) - .limit(limit) + select(AugmentedPart).order_by(AugmentedPart.description).offset(skip).limit(limit) ) return list(result.scalars().all()) @@ -89,19 +84,11 @@ async def list_filtered( direction: str = "asc", skip: int = 0, limit: int = 100, - match_sku: bool = False, ) -> list[AugmentedPart]: q = select(AugmentedPart) if description.strip(): term = f"%{description.strip()}%" - if match_sku: - q = q.where( - or_( - AugmentedPart.description.ilike(term), AugmentedPart.sku.ilike(term) - ) - ) - else: - q = q.where(AugmentedPart.description.ilike(term)) + q = q.where(or_(AugmentedPart.description.ilike(term), AugmentedPart.sku.ilike(term))) if colour_id is not None: q = q.where(AugmentedPart.colour_id == colour_id) if manufacturer_id is not None: @@ -130,7 +117,8 @@ async def count_filtered( q = select(func.count()).select_from(AugmentedPart) if description.strip(): - q = q.where(AugmentedPart.description.ilike(f"%{description.strip()}%")) + term = f"%{description.strip()}%" + q = q.where(or_(AugmentedPart.description.ilike(term), AugmentedPart.sku.ilike(term))) if colour_id is not None: q = q.where(AugmentedPart.colour_id == colour_id) if manufacturer_id is not None: @@ -153,17 +141,13 @@ async def get_unlinked_imported_parts_filtered( limit: int = 50, ) -> list[ImportedPart]: q = select(ImportedPart).where( - ImportedPart.InventoryID.not_in( - select(augmented_part_imported_parts.c.imported_part_id) - ) + ImportedPart.InventoryID.not_in(select(augmented_part_imported_parts.c.imported_part_id)) ) if description.strip(): q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%")) if colour.strip(): q = q.where(ImportedPart.TBXColour.ilike(f"%{colour.strip()}%")) - result = await db.execute( - q.order_by(ImportedPart.InventoryID).offset(skip).limit(limit) - ) + result = await db.execute(q.order_by(ImportedPart.InventoryID).offset(skip).limit(limit)) return list(result.scalars().all()) diff --git a/app/crud/imported_part.py b/app/crud/imported_part.py index 1184aed..0add65d 100644 --- a/app/crud/imported_part.py +++ b/app/crud/imported_part.py @@ -1,4 +1,4 @@ -from sqlalchemy import distinct, insert, select, update +from sqlalchemy import distinct, insert, or_, select, update from sqlalchemy.ext.asyncio import AsyncSession from app.models.imported_part import ImportedPart @@ -61,7 +61,10 @@ async def list_filtered( ) -> list[ImportedPart]: q = select(ImportedPart) if description.strip(): - q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%")) + term = f"%{description.strip()}%" + q = q.where( + or_(ImportedPart.Description.ilike(term), ImportedPart.InventoryID.ilike(term)) + ) q = _apply_sort(q, sort, direction) result = await db.execute(q.offset(skip).limit(limit)) return list(result.scalars().all()) @@ -88,7 +91,10 @@ async def count_filtered(db: AsyncSession, description: str = "") -> int: q = select(func.count()).select_from(ImportedPart) if description.strip(): - q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%")) + term = f"%{description.strip()}%" + q = q.where( + or_(ImportedPart.Description.ilike(term), ImportedPart.InventoryID.ilike(term)) + ) result = await db.execute(q) return result.scalar_one() diff --git a/app/services/scope_guide.py b/app/services/scope_guide.py index 52cc129..62a4154 100644 --- a/app/services/scope_guide.py +++ b/app/services/scope_guide.py @@ -72,7 +72,6 @@ async def build( sub_part_type_id=sub_part_type_id, part_type_id=scope_type.id, limit=1000, - match_sku=True, ) scope_ids = {s.id for s in scopes} if not scope_ids: diff --git a/app/templates/admin/_augmented_parts_table.html b/app/templates/admin/_augmented_parts_table.html index 22e4da2..c0a9bfc 100644 --- a/app/templates/admin/_augmented_parts_table.html +++ b/app/templates/admin/_augmented_parts_table.html @@ -12,7 +12,7 @@ hx-swap="outerHTML"> - + - + diff --git a/tests/test_admin_sorting.py b/tests/test_admin_sorting.py index 63223ad..0fb0d3a 100644 --- a/tests/test_admin_sorting.py +++ b/tests/test_admin_sorting.py @@ -90,6 +90,23 @@ async def test_augmented_parts_filter_sorts_by_sku_asc( ] +@pytest.mark.asyncio +async def test_augmented_parts_filter_matches_by_sku( + client, seeded, make_user, db_sessionmaker +): + async with db_sessionmaker() as db: + await _seed_augmented_parts(db) + await make_user("reader2b@example.com", role_ids=[seeded["roles"]["user"].id]) + await _web_login(client, "reader2b@example.com") + + resp = await client.get("/admin/augmented-parts/filter?description=SKU-B") + assert resp.status_code == 200 + body = resp.text + assert "Charlie scope" in body + assert "Alpha scope" not in body + assert "Bravo scope" not in body + + @pytest.mark.asyncio async def test_imported_parts_page_sorts_by_description_desc( client, seeded, make_user, db_sessionmaker @@ -109,6 +126,23 @@ async def test_imported_parts_page_sorts_by_description_desc( ] +@pytest.mark.asyncio +async def test_imported_parts_filter_matches_by_inventory_id( + client, seeded, make_user, db_sessionmaker +): + async with db_sessionmaker() as db: + await _seed_imported_parts(db) + await make_user("reader4@example.com", role_ids=[seeded["roles"]["user"].id]) + await _web_login(client, "reader4@example.com") + + resp = await client.get("/admin/imported-parts/filter?description=INV-3") + assert resp.status_code == 200 + body = resp.text + assert "Bravo widget" in body + assert "Alpha widget" not in body + assert "Charlie widget" not in body + + @pytest.mark.asyncio async def test_list_filtered_sorts_by_joined_colour_name(db_sessionmaker): async with db_sessionmaker() as db: