diff --git a/app/crud/augmented_part.py b/app/crud/augmented_part.py index 456f2c6..3257876 100644 --- a/app/crud/augmented_part.py +++ b/app/crud/augmented_part.py @@ -1,7 +1,9 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession +from app.models.associations import augmented_part_imported_parts from app.models.augmented_part import AugmentedPart +from app.models.imported_part import ImportedPart _RELATIONSHIPS = ["colour", "manufacturer", "scope_model", "part_type", "sub_part_type"] @@ -58,6 +60,20 @@ async def update( return ap +async def get_unlinked_imported_parts(db: AsyncSession) -> list[ImportedPart]: + """ImportedParts that have no corresponding AugmentedPart link.""" + result = await db.execute( + select(ImportedPart) + .where( + ImportedPart.InventoryID.not_in( + select(augmented_part_imported_parts.c.imported_part_id) + ) + ) + .order_by(ImportedPart.InventoryID) + ) + return list(result.scalars().all()) + + async def delete(db: AsyncSession, ap: AugmentedPart) -> None: await db.delete(ap) await db.commit() diff --git a/app/services/augmented_part_sync.py b/app/services/augmented_part_sync.py index e51e0d1..b7a6b43 100644 --- a/app/services/augmented_part_sync.py +++ b/app/services/augmented_part_sync.py @@ -32,6 +32,51 @@ from app.models.part_type import PartType from app.schemas.augmented_part import AugmentedPartSyncResult +async def sync_one( + db: AsyncSession, + *, + inventory_id: str, + part_type_id: int, + sub_part_type_id: int | None, +) -> AugmentedPart: + """Create an AugmentedPart for a single ImportedPart with manually supplied type.""" + ip = await db.get(ImportedPart, inventory_id) + if ip is None: + raise ValueError(f"ImportedPart {inventory_id!r} not found") + + description = (ip.Description or "").strip() or ip.InventoryID + + colour_name = (ip.TBXColour or "").strip() or "Unknown" + colour = await part_colour_crud.get_by_name(db, colour_name) + if colour is None: + colour = await part_colour_crud.create(db, name=colour_name) + + manufacturer = await manufacturer_crud.get_by_name(db, "Armament") + if manufacturer is None: + manufacturer = await manufacturer_crud.create(db, name="Armament", url="") + + ap = AugmentedPart( + sku=None, + description=description, + colour_id=colour.id, + manufacturer_id=manufacturer.id, + scope_model_id=None, + part_type_id=part_type_id, + sub_part_type_id=sub_part_type_id, + ) + db.add(ap) + await db.flush() + + await db.execute( + augmented_part_imported_parts.insert().values( + augmented_part_id=ap.id, + imported_part_id=ip.InventoryID, + ) + ) + await db.commit() + return ap + + def _infer_part_type(description: str, part_types: list[PartType]) -> PartType | None: """Return the best-matching PartType or None. diff --git a/app/templates/admin/_augmented_parts_sections.html b/app/templates/admin/_augmented_parts_sections.html new file mode 100644 index 0000000..0ee9d83 --- /dev/null +++ b/app/templates/admin/_augmented_parts_sections.html @@ -0,0 +1,4 @@ +
These parts were skipped during sync because no Part Type could be inferred from their description. Assign a type and click Sync to create the Augmented Part entry.
+ {% if unlinked_sync_result %}{{ unlinked_sync_result }}
{% endif %} + {% if unlinked_sync_error %}{{ unlinked_sync_error }}
{% endif %} +| Inventory ID | +Description | +Colour | +Type | +Sub Type | ++ |
|---|---|---|---|---|---|
{{ ip.InventoryID }} |
+ {{ ip.Description | truncate(60) if ip.Description else '—' }} | +{{ ip.TBXColour or '—' }} | ++ + | ++ + | ++ + | +
Creates Augmented Part entries for new Imported Parts, and removes entries for deleted Imported Parts.
-