from __future__ import annotations from app.repositories.pricing_cleanup import ( delete_pricing_categories_by_ids, list_pricing_categories_with_rule_counts, ) def _matches_any_prefix(value: str | None, prefixes: list[str]) -> list[str]: if not value: return [] matches: list[str] = [] lower_value = value.lower() for prefix in prefixes: if lower_value.startswith(prefix.lower()): matches.append(prefix) return matches async def build_pricing_cleanup_preview( *, scheme_id: str, code_prefixes: list[str], name_prefixes: list[str], pricing_category_ids: list[str], delete_only_without_rules: bool, ) -> dict: rows = await list_pricing_categories_with_rule_counts(scheme_id=scheme_id) requested_ids = set(pricing_category_ids) items: list[dict] = [] safe_to_delete_count = 0 for row in rows: matched_by: list[str] = [] for prefix in _matches_any_prefix(row["code"], code_prefixes): matched_by.append(f"code_prefix:{prefix}") for prefix in _matches_any_prefix(row["name"], name_prefixes): matched_by.append(f"name_prefix:{prefix}") if row["pricing_category_id"] in requested_ids: matched_by.append("pricing_category_id") if not matched_by: continue deletable = True if delete_only_without_rules and row["rules_count"] > 0: deletable = False if deletable: safe_to_delete_count += 1 items.append( { "pricing_category_id": row["pricing_category_id"], "name": row["name"], "code": row["code"], "rules_count": row["rules_count"], "matched_by": matched_by, "deletable": deletable, } ) return { "scheme_id": scheme_id, "code_prefixes": code_prefixes, "name_prefixes": name_prefixes, "pricing_category_ids": pricing_category_ids, "delete_only_without_rules": delete_only_without_rules, "total_candidates": len(items), "safe_to_delete_count": safe_to_delete_count, "items": items, } async def execute_pricing_cleanup( *, scheme_id: str, code_prefixes: list[str], name_prefixes: list[str], pricing_category_ids: list[str], delete_only_without_rules: bool, dry_run: bool, ) -> dict: preview = await build_pricing_cleanup_preview( scheme_id=scheme_id, code_prefixes=code_prefixes, name_prefixes=name_prefixes, pricing_category_ids=pricing_category_ids, delete_only_without_rules=delete_only_without_rules, ) deletable_items = [item for item in preview["items"] if item["deletable"]] skipped_items = [item for item in preview["items"] if not item["deletable"]] would_delete_ids = [item["pricing_category_id"] for item in deletable_items] deleted_ids: list[str] = [] if not dry_run and would_delete_ids: await delete_pricing_categories_by_ids( scheme_id=scheme_id, pricing_category_ids=would_delete_ids, ) deleted_ids = list(would_delete_ids) return { "scheme_id": scheme_id, "dry_run": dry_run, "delete_only_without_rules": delete_only_without_rules, "requested_total": len(pricing_category_ids) + len(code_prefixes) + len(name_prefixes), "matched_total": preview["total_candidates"], "would_delete_count": len(would_delete_ids), "deleted_count": 0 if dry_run else len(deleted_ids), "skipped_count": len(skipped_items), "would_delete_category_ids": would_delete_ids, "deleted_category_ids": deleted_ids, "skipped": [ { "pricing_category_id": item["pricing_category_id"], "reason": "category_has_rules", } for item in skipped_items ], }