dingdanquanliucheng/backend/scripts/cleanup_categories.py
taiyi 7356acec89 feat: 添加产品分类清理脚本
一次性脚本,将多余分类的产品迁移到工业品/日用品,然后软删除多余分类。
运行方式: python -m backend.scripts.cleanup_categories

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-05 11:06:00 +08:00

158 lines
5.3 KiB
Python

"""清理多余产品分类脚本。
将数据库中多余的分类迁移到工业品(1)或日用品(2),然后软删除多余分类。
运行方式: python -m backend.scripts.cleanup_categories
"""
from pathlib import Path
import sys
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from sqlalchemy import select, func
from backend.app.db import SessionLocal
from backend.app.models.business import Product, ProductCategory
# 保留的分类 ID
KEEP_IDS = {1, 2}
# 工业关键词:产品名包含这些词的归入工业品
INDUSTRY_KEYWORDS = ["", "", "", "", "", "", "", "", "", "", "", "", "", "线", "", "", ""]
def classify_product(product_name: str) -> int:
"""根据产品名判断归入工业品(1)还是日用品(2)"""
for kw in INDUSTRY_KEYWORDS:
if kw in product_name:
return 1
return 2
def main():
session = SessionLocal()
try:
# ========== Step 1: 查询当前状态 ==========
print("=" * 60)
print("产品分类清理脚本")
print("=" * 60)
categories = session.execute(
select(ProductCategory).where(ProductCategory.deleted == 0).order_by(ProductCategory.id)
).scalars().all()
print(f"\n当前分类列表(共 {len(categories)} 个):")
print("-" * 50)
extra_categories = []
for cat in categories:
product_count = session.execute(
select(func.count(Product.id)).where(
Product.category_id == cat.id,
Product.deleted == 0,
)
).scalar() or 0
tag = "✓ 保留" if cat.id in KEEP_IDS else "✗ 待删除"
print(f" ID={cat.id} {cat.category_name:<12} ({cat.category_code}) 产品数: {product_count} [{tag}]")
if cat.id not in KEEP_IDS:
extra_categories.append((cat, product_count))
if not extra_categories:
print("\n没有需要清理的多余分类。")
return
# 统计需要迁移的产品
total_products_to_migrate = sum(count for _, count in extra_categories)
print(f"\n需要迁移的产品总数: {total_products_to_migrate}")
print(f"需要删除的分类数: {len(extra_categories)}")
# 显示迁移计划
print("\n迁移计划:")
print("-" * 50)
for cat, count in extra_categories:
products = session.execute(
select(Product).where(
Product.category_id == cat.id,
Product.deleted == 0,
)
).scalars().all()
industry_count = sum(1 for p in products if classify_product(p.product_name) == 1)
daily_count = count - industry_count
print(f" 分类「{cat.category_name}」({count}个产品):")
print(f" → 工业品: {industry_count}")
print(f" → 日用品: {daily_count}")
for p in products:
target = "工业品" if classify_product(p.product_name) == 1 else "日用品"
print(f" - {p.product_name} ({p.specification}) → {target}")
# ========== Step 2: 确认 ==========
print("\n" + "=" * 60)
confirm = input("确认执行清理?(输入 yes 继续): ").strip()
if confirm.lower() != "yes":
print("已取消。")
return
# ========== Step 3: 执行迁移 ==========
print("\n执行迁移...")
migrated = 0
for cat, count in extra_categories:
products = session.execute(
select(Product).where(
Product.category_id == cat.id,
Product.deleted == 0,
)
).scalars().all()
for p in products:
target_id = classify_product(p.product_name)
target_name = "工业品" if target_id == 1 else "日用品"
p.category_id = target_id
p.category = target_name
migrated += 1
print(f" 迁移: {p.product_name}{target_name}")
# ========== Step 4: 软删除多余分类 ==========
print("\n删除多余分类...")
deleted = 0
for cat, _ in extra_categories:
cat.deleted = 1
deleted += 1
print(f" 删除: {cat.category_name}")
session.commit()
# ========== Step 5: 验证 ==========
print("\n" + "=" * 60)
print("清理完成!验证结果:")
remaining = session.execute(
select(ProductCategory).where(ProductCategory.deleted == 0).order_by(ProductCategory.id)
).scalars().all()
print(f"剩余分类数: {len(remaining)}")
for cat in remaining:
product_count = session.execute(
select(func.count(Product.id)).where(
Product.category_id == cat.id,
Product.deleted == 0,
)
).scalar() or 0
print(f" ID={cat.id} {cat.category_name} 产品数: {product_count}")
print(f"\n共迁移 {migrated} 个产品,删除 {deleted} 个分类。")
finally:
session.close()
if __name__ == "__main__":
main()