feat(catalog): auto-purge orphan materials when quotes/items release them
A material that ends up referenced by nothing (no line_items anywhere, no warehouse stock) was previously left behind forever, accumulating dead catalog rows as offers were edited or deleted. - new quote_service.cleanup_orphan_materials(db, material_ids): for each candidate it checks usage across line_items (all quotes) and warehouse inventory; only when both are zero does it remove the auxiliary rows (supplier_materials, brand_materials, material_units, material_attribute_values) and finally the materials row; returns the purged ids - replace_quote_children (edit modal): snapshots the quote's material ids before the children are replaced, diffs against the incoming items, and purges whatever was detached - delete_quote: collects the quote's material ids + attachment keys first, deletes the quote, purges orphans inside the transaction, and only then (post-commit) removes the MinIO objects — now both the recorded attachment keys and a recursive remove_prefix(quote_id/) sweep, so stray objects under the quote folder cannot survive; the response's cascaded block reports purged_orphan_materials - line_items: PATCH that switches a line item's material, and DELETE of a line item, both clean up the previously referenced material - storage.remove_prefix(prefix): best-effort recursive object deletion under a prefix (sync MinIO client wrapped in asyncio.to_thread, same pattern as remove_object) Safety: usage checks run against the whole system before any delete, so shared materials — including those held only by warehouse stock — are never touched. Verified: python compile + api image build clean.
This commit is contained in:
@@ -66,3 +66,22 @@ async def remove_object(key: str) -> None:
|
||||
await asyncio.to_thread(get_client().remove_object, settings.MINIO_BUCKET, key)
|
||||
except Exception:
|
||||
logger.warning("Could not remove MinIO object %r — ignoring", key)
|
||||
|
||||
|
||||
async def remove_prefix(prefix: str) -> None:
|
||||
"""Delete all objects matching a prefix in the bucket (best-effort)."""
|
||||
client = get_client()
|
||||
|
||||
def _sync_remove_prefix():
|
||||
try:
|
||||
objects = client.list_objects(settings.MINIO_BUCKET, prefix=prefix, recursive=True)
|
||||
for obj in objects:
|
||||
try:
|
||||
client.remove_object(settings.MINIO_BUCKET, obj.object_name)
|
||||
except Exception:
|
||||
logger.warning("Could not remove MinIO object %r under prefix %r", obj.object_name, prefix)
|
||||
except Exception:
|
||||
logger.warning("Could not list MinIO objects under prefix %r — ignoring", prefix)
|
||||
|
||||
await asyncio.to_thread(_sync_remove_prefix)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user