Making text search apply to both description and sku in imported parts and augmented parts
This commit is contained in:
1 parent
292830ae0f
commit
ce2bdc43a8
7 files changed
+53
-30
No files matched your search
@@ -8,7 +8,7 @@ seed:
|
|||||||
uv run alembic upgrade head
|
uv run alembic upgrade head
|
||||||
uv run python -m scripts.seed
|
uv run python -m scripts.seed
|
||||||
|
|
||||||
dev:
|
dev: migrate
|
||||||
uv run uvicorn app.main:app --reload
|
uv run uvicorn app.main:app --reload
|
||||||
open http://localhost:8000
|
open http://localhost:8000
|
||||||
|
|
||||||
|
|||||||
@@ -57,14 +57,9 @@ async def get_by_sku(db: AsyncSession, sku: str) -> AugmentedPart | None:
|
|||||||
return result.scalars().first()
|
return result.scalars().first()
|
||||||
|
|
||||||
|
|
||||||
async def list_all(
|
async def list_all(db: AsyncSession, skip: int = 0, limit: int = 100) -> list[AugmentedPart]:
|
||||||
db: AsyncSession, skip: int = 0, limit: int = 100
|
|
||||||
) -> list[AugmentedPart]:
|
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(AugmentedPart)
|
select(AugmentedPart).order_by(AugmentedPart.description).offset(skip).limit(limit)
|
||||||
.order_by(AugmentedPart.description)
|
|
||||||
.offset(skip)
|
|
||||||
.limit(limit)
|
|
||||||
)
|
)
|
||||||
return list(result.scalars().all())
|
return list(result.scalars().all())
|
||||||
|
|
||||||
@@ -89,19 +84,11 @@ async def list_filtered(
|
|||||||
direction: str = "asc",
|
direction: str = "asc",
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
match_sku: bool = False,
|
|
||||||
) -> list[AugmentedPart]:
|
) -> list[AugmentedPart]:
|
||||||
q = select(AugmentedPart)
|
q = select(AugmentedPart)
|
||||||
if description.strip():
|
if description.strip():
|
||||||
term = f"%{description.strip()}%"
|
term = f"%{description.strip()}%"
|
||||||
if match_sku:
|
q = q.where(or_(AugmentedPart.description.ilike(term), AugmentedPart.sku.ilike(term)))
|
||||||
q = q.where(
|
|
||||||
or_(
|
|
||||||
AugmentedPart.description.ilike(term), AugmentedPart.sku.ilike(term)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
q = q.where(AugmentedPart.description.ilike(term))
|
|
||||||
if colour_id is not None:
|
if colour_id is not None:
|
||||||
q = q.where(AugmentedPart.colour_id == colour_id)
|
q = q.where(AugmentedPart.colour_id == colour_id)
|
||||||
if manufacturer_id is not None:
|
if manufacturer_id is not None:
|
||||||
@@ -130,7 +117,8 @@ async def count_filtered(
|
|||||||
|
|
||||||
q = select(func.count()).select_from(AugmentedPart)
|
q = select(func.count()).select_from(AugmentedPart)
|
||||||
if description.strip():
|
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:
|
if colour_id is not None:
|
||||||
q = q.where(AugmentedPart.colour_id == colour_id)
|
q = q.where(AugmentedPart.colour_id == colour_id)
|
||||||
if manufacturer_id is not None:
|
if manufacturer_id is not None:
|
||||||
@@ -153,17 +141,13 @@ async def get_unlinked_imported_parts_filtered(
|
|||||||
limit: int = 50,
|
limit: int = 50,
|
||||||
) -> list[ImportedPart]:
|
) -> list[ImportedPart]:
|
||||||
q = select(ImportedPart).where(
|
q = select(ImportedPart).where(
|
||||||
ImportedPart.InventoryID.not_in(
|
ImportedPart.InventoryID.not_in(select(augmented_part_imported_parts.c.imported_part_id))
|
||||||
select(augmented_part_imported_parts.c.imported_part_id)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if description.strip():
|
if description.strip():
|
||||||
q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%"))
|
q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%"))
|
||||||
if colour.strip():
|
if colour.strip():
|
||||||
q = q.where(ImportedPart.TBXColour.ilike(f"%{colour.strip()}%"))
|
q = q.where(ImportedPart.TBXColour.ilike(f"%{colour.strip()}%"))
|
||||||
result = await db.execute(
|
result = await db.execute(q.order_by(ImportedPart.InventoryID).offset(skip).limit(limit))
|
||||||
q.order_by(ImportedPart.InventoryID).offset(skip).limit(limit)
|
|
||||||
)
|
|
||||||
return list(result.scalars().all())
|
return list(result.scalars().all())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.models.imported_part import ImportedPart
|
from app.models.imported_part import ImportedPart
|
||||||
@@ -61,7 +61,10 @@ async def list_filtered(
|
|||||||
) -> list[ImportedPart]:
|
) -> list[ImportedPart]:
|
||||||
q = select(ImportedPart)
|
q = select(ImportedPart)
|
||||||
if description.strip():
|
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)
|
q = _apply_sort(q, sort, direction)
|
||||||
result = await db.execute(q.offset(skip).limit(limit))
|
result = await db.execute(q.offset(skip).limit(limit))
|
||||||
return list(result.scalars().all())
|
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)
|
q = select(func.count()).select_from(ImportedPart)
|
||||||
if description.strip():
|
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)
|
result = await db.execute(q)
|
||||||
return result.scalar_one()
|
return result.scalar_one()
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ async def build(
|
|||||||
sub_part_type_id=sub_part_type_id,
|
sub_part_type_id=sub_part_type_id,
|
||||||
part_type_id=scope_type.id,
|
part_type_id=scope_type.id,
|
||||||
limit=1000,
|
limit=1000,
|
||||||
match_sku=True,
|
|
||||||
)
|
)
|
||||||
scope_ids = {s.id for s in scopes}
|
scope_ids = {s.id for s in scopes}
|
||||||
if not scope_ids:
|
if not scope_ids:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
<input type="hidden" name="sort" value="{{ sort }}">
|
<input type="hidden" name="sort" value="{{ sort }}">
|
||||||
<input type="hidden" name="dir" value="{{ dir }}">
|
<input type="hidden" name="dir" value="{{ dir }}">
|
||||||
<input type="text" name="description" placeholder="Filter by description…" value="{{ filter_description or '' }}">
|
<input type="text" name="description" placeholder="Filter by description or SKU…" value="{{ filter_description or '' }}">
|
||||||
<select name="colour_id">
|
<select name="colour_id">
|
||||||
<option value="">All colours</option>
|
<option value="">All colours</option>
|
||||||
{% for pc in part_colours %}
|
{% for pc in part_colours %}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML">
|
||||||
<input type="hidden" name="sort" value="{{ sort }}">
|
<input type="hidden" name="sort" value="{{ sort }}">
|
||||||
<input type="hidden" name="dir" value="{{ dir }}">
|
<input type="hidden" name="dir" value="{{ dir }}">
|
||||||
<input type="text" name="description" placeholder="Filter by description…" value="{{ filter_description or '' }}">
|
<input type="text" name="description" placeholder="Filter by description or SKU…" value="{{ filter_description or '' }}">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
@pytest.mark.asyncio
|
||||||
async def test_imported_parts_page_sorts_by_description_desc(
|
async def test_imported_parts_page_sorts_by_description_desc(
|
||||||
client, seeded, make_user, db_sessionmaker
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_list_filtered_sorts_by_joined_colour_name(db_sessionmaker):
|
async def test_list_filtered_sorts_by_joined_colour_name(db_sessionmaker):
|
||||||
async with db_sessionmaker() as db:
|
async with db_sessionmaker() as db:
|
||||||
|
|||||||
Reference in new issue
Block a user