diff --git a/alembic/versions/4d5e6f7a8b9c_augmented_parts_on_website_preferred.py b/alembic/versions/4d5e6f7a8b9c_augmented_parts_on_website_preferred.py
new file mode 100644
index 0000000..6eb9b4c
--- /dev/null
+++ b/alembic/versions/4d5e6f7a8b9c_augmented_parts_on_website_preferred.py
@@ -0,0 +1,41 @@
+"""augmented_parts_on_website_preferred
+
+Revision ID: 4d5e6f7a8b9c
+Revises: 3c4d5e6f7a8b
+Create Date: 2026-09-21 00:00:00.000000
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision: str = "4d5e6f7a8b9c"
+down_revision: str | None = "3c4d5e6f7a8b"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+ with op.batch_alter_table("augmented_parts", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column(
+ "on_website", sa.Boolean(), nullable=False, server_default=sa.false()
+ )
+ )
+ batch_op.add_column(
+ sa.Column(
+ "preferred_part",
+ sa.Boolean(),
+ nullable=False,
+ server_default=sa.false(),
+ )
+ )
+
+
+def downgrade() -> None:
+ with op.batch_alter_table("augmented_parts", schema=None) as batch_op:
+ batch_op.drop_column("preferred_part")
+ batch_op.drop_column("on_website")
diff --git a/app/api/augmented_parts.py b/app/api/augmented_parts.py
index 28ba812..11bd6cc 100644
--- a/app/api/augmented_parts.py
+++ b/app/api/augmented_parts.py
@@ -59,6 +59,8 @@ async def create_augmented_part(
scope_model_id=payload.scope_model_id,
part_type_id=payload.part_type_id,
sub_part_type_id=payload.sub_part_type_id,
+ on_website=payload.on_website,
+ preferred_part=payload.preferred_part,
)
await audit_crud.log(
db,
diff --git a/app/crud/augmented_part.py b/app/crud/augmented_part.py
index b3a4f89..164634f 100644
--- a/app/crud/augmented_part.py
+++ b/app/crud/augmented_part.py
@@ -10,7 +10,14 @@ from app.models.part_type import PartType
from app.models.scope_model import ScopeModel
from app.models.sub_part_type import SubPartType
-_RELATIONSHIPS = ["colour", "manufacturer", "scope_model", "part_type", "sub_part_type"]
+_RELATIONSHIPS = [
+ "colour",
+ "manufacturer",
+ "scope_model",
+ "part_type",
+ "sub_part_type",
+ "notes",
+]
# Sortable columns for the admin list, keyed by the query-string "sort" value.
# Columns backed by a related table's name require an outer join (the FK may be
@@ -87,6 +94,7 @@ async def list_filtered(
part_type_id: int | None = None,
sub_part_type_id: int | None = None,
include_archived: bool = False,
+ on_website: bool = False,
sort: str = "description",
direction: str = "asc",
skip: int = 0,
@@ -110,6 +118,8 @@ async def list_filtered(
q = q.where(AugmentedPart.sub_part_type_id == sub_part_type_id)
if not include_archived:
q = q.where(AugmentedPart.archived.is_(False))
+ if on_website:
+ q = q.where(AugmentedPart.on_website.is_(True))
q = _apply_sort(q, sort, direction)
result = await db.execute(q.offset(skip).limit(limit))
return list(result.scalars().all())
@@ -124,6 +134,7 @@ async def count_filtered(
part_type_id: int | None = None,
sub_part_type_id: int | None = None,
include_archived: bool = False,
+ on_website: bool = False,
) -> int:
from sqlalchemy import func
@@ -145,6 +156,8 @@ async def count_filtered(
q = q.where(AugmentedPart.sub_part_type_id == sub_part_type_id)
if not include_archived:
q = q.where(AugmentedPart.archived.is_(False))
+ if on_website:
+ q = q.where(AugmentedPart.on_website.is_(True))
result = await db.execute(q)
return result.scalar_one()
@@ -220,6 +233,8 @@ async def create(
scope_model_id: int | None,
part_type_id: int,
sub_part_type_id: int | None,
+ on_website: bool = False,
+ preferred_part: bool = False,
) -> AugmentedPart:
ap = AugmentedPart(
sku=sku,
@@ -229,6 +244,8 @@ async def create(
scope_model_id=scope_model_id,
part_type_id=part_type_id,
sub_part_type_id=sub_part_type_id,
+ on_website=on_website,
+ preferred_part=preferred_part,
)
db.add(ap)
await db.commit()
diff --git a/app/models/augmented_part.py b/app/models/augmented_part.py
index d506dea..0a1f02e 100644
--- a/app/models/augmented_part.py
+++ b/app/models/augmented_part.py
@@ -47,6 +47,8 @@ class AugmentedPart(Base):
# removed, so fit/attachment-point history is preserved. Hidden from list
# views by default.
archived: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
+ on_website: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
+ preferred_part: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
# linked imported parts — not eagerly loaded; used by sync service only
imported_parts: Mapped[list[ImportedPart]] = relationship(
diff --git a/app/schemas/augmented_part.py b/app/schemas/augmented_part.py
index 1a393ae..a100750 100644
--- a/app/schemas/augmented_part.py
+++ b/app/schemas/augmented_part.py
@@ -39,6 +39,8 @@ class AugmentedPartBase(BaseModel):
scope_model_id: int | None = None
part_type_id: int
sub_part_type_id: int | None = None
+ on_website: bool = False
+ preferred_part: bool = False
class AugmentedPartCreate(AugmentedPartBase):
@@ -54,6 +56,8 @@ class AugmentedPartUpdate(BaseModel):
part_type_id: int | None = None
sub_part_type_id: int | None = None
archived: bool | None = None
+ on_website: bool | None = None
+ preferred_part: bool | None = None
class AugmentedPartSyncResult(BaseModel):
diff --git a/app/templates/admin/_augmented_part_row.html b/app/templates/admin/_augmented_part_row.html
index da689f2..4b4c07d 100644
--- a/app/templates/admin/_augmented_part_row.html
+++ b/app/templates/admin/_augmented_part_row.html
@@ -8,6 +8,8 @@
{{ ap.part_type.name }} |
{{ ap.sub_part_type.name if ap.sub_part_type else '—' }} |
{% if ap.archived %}Archived{% else %}—{% endif %} |
+ {% if ap.on_website %}Yes{% else %}—{% endif %} |
+ {% if ap.preferred_part %}Yes{% else %}—{% endif %} |
|