2026-05-30 07:23:33 +08:00
|
|
|
|
"""
|
|
|
|
|
|
产品管理路由模块
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
处理产品和产品分类的增删改查接口,URL 前缀为空(路由在路径中定义)。
|
|
|
|
|
|
包括:产品分类的列表/创建/更新、产品的列表/创建/更新/详情、
|
|
|
|
|
|
产品规格的更新和默认规格设置。
|
|
|
|
|
|
|
|
|
|
|
|
注意:本模块使用 /api/product-categories 和 /api/products 作为路径前缀,
|
|
|
|
|
|
而非使用 APIRouter(prefix=...) 统一设置。
|
|
|
|
|
|
"""
|
2026-06-02 20:45:00 +08:00
|
|
|
|
from fastapi import APIRouter, Body, Depends, Query
|
|
|
|
|
|
from sqlalchemy import func, select
|
2026-05-14 23:28:09 +08:00
|
|
|
|
from sqlalchemy.orm import Session
|
2026-05-14 13:51:06 +08:00
|
|
|
|
|
2026-06-15 13:18:43 +08:00
|
|
|
|
from backend.app.api.deps import get_product_service, require_roles
|
|
|
|
|
|
from backend.app.core.cache import cache_delete_pattern
|
|
|
|
|
|
from backend.app.core.error_codes import ErrorCode
|
2026-06-02 20:45:00 +08:00
|
|
|
|
from backend.app.core.exceptions import AppException
|
2026-05-14 23:28:09 +08:00
|
|
|
|
from backend.app.db import get_db_session
|
2026-06-02 20:45:00 +08:00
|
|
|
|
from backend.app.models.business import Product, ProductCategory, ProductPricingRule
|
2026-05-14 13:51:06 +08:00
|
|
|
|
from backend.app.schemas.common import success_payload
|
2026-05-15 10:47:04 +08:00
|
|
|
|
from backend.app.schemas.products import (
|
|
|
|
|
|
CreateCategoryRequest,
|
|
|
|
|
|
CreateProductRequest,
|
|
|
|
|
|
UpdateCategoryRequest,
|
|
|
|
|
|
UpdateProductCategoryRequest,
|
2026-05-21 14:59:38 +08:00
|
|
|
|
UpdateProductRequest,
|
|
|
|
|
|
UpdateProductSpecificationRequest,
|
2026-05-15 10:47:04 +08:00
|
|
|
|
)
|
2026-05-14 23:28:09 +08:00
|
|
|
|
from backend.app.services.product_service import ProductService
|
2026-05-14 13:51:06 +08:00
|
|
|
|
|
|
|
|
|
|
router = APIRouter(tags=["products"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/product-categories")
|
2026-05-14 23:28:09 +08:00
|
|
|
|
def list_categories(
|
2026-05-30 07:23:33 +08:00
|
|
|
|
category_name: str | None = Query(default=None), # 分类名称模糊搜索
|
|
|
|
|
|
status: int | None = Query(default=None), # 分类状态筛选(启用/停用)
|
|
|
|
|
|
page_no: int = Query(default=1), # 页码,默认第 1 页
|
|
|
|
|
|
page_size: int = Query(default=20), # 每页条数,默认 20 条
|
|
|
|
|
|
product_service: ProductService = Depends(get_product_service), # 注入产品服务
|
|
|
|
|
|
session: Session = Depends(get_db_session), # 注入数据库会话
|
2026-05-14 23:28:09 +08:00
|
|
|
|
) -> dict:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""分页查询产品分类列表
|
|
|
|
|
|
|
|
|
|
|
|
用途:获取所有产品分类,支持按名称和状态筛选。
|
|
|
|
|
|
请求参数:Query 参数筛选 + 分页参数。
|
|
|
|
|
|
返回值:分页分类列表,包含 total、page_no、page_size、list。
|
|
|
|
|
|
权限要求:无特殊权限限制(未配置角色鉴权)。
|
|
|
|
|
|
"""
|
2026-05-14 23:28:09 +08:00
|
|
|
|
result = product_service.list_categories(
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
filters={"category_name": category_name, "status": status},
|
|
|
|
|
|
)
|
|
|
|
|
|
start = max(page_no - 1, 0) * page_size
|
|
|
|
|
|
result["list"] = result["list"][start : start + page_size]
|
|
|
|
|
|
result["page_no"] = page_no
|
|
|
|
|
|
result["page_size"] = page_size
|
|
|
|
|
|
return success_payload(result)
|
2026-05-14 13:51:06 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/api/product-categories")
|
2026-05-14 23:28:09 +08:00
|
|
|
|
def create_category(
|
2026-05-30 07:23:33 +08:00
|
|
|
|
payload: CreateCategoryRequest, # 创建分类的请求体
|
|
|
|
|
|
product_service: ProductService = Depends(get_product_service), # 注入产品服务
|
|
|
|
|
|
session: Session = Depends(get_db_session), # 注入数据库会话
|
2026-05-14 23:28:09 +08:00
|
|
|
|
) -> dict:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""创建产品分类
|
|
|
|
|
|
|
|
|
|
|
|
用途:新增一个产品分类。
|
|
|
|
|
|
请求参数:CreateCategoryRequest(分类名称、描述等)。
|
|
|
|
|
|
返回值:创建成功后的分类信息。
|
|
|
|
|
|
权限要求:无特殊权限限制(未配置角色鉴权)。
|
|
|
|
|
|
"""
|
2026-05-14 23:28:09 +08:00
|
|
|
|
return success_payload(product_service.create_category(payload.model_dump(), session))
|
2026-05-14 13:51:06 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/api/product-categories/{category_id}")
|
2026-05-14 23:28:09 +08:00
|
|
|
|
def update_category(
|
2026-05-30 07:23:33 +08:00
|
|
|
|
category_id: int, # 分类 ID(路径参数)
|
|
|
|
|
|
payload: UpdateProductCategoryRequest, # 更新分类的请求体
|
|
|
|
|
|
product_service: ProductService = Depends(get_product_service), # 注入产品服务
|
|
|
|
|
|
session: Session = Depends(get_db_session), # 注入数据库会话
|
2026-05-14 23:28:09 +08:00
|
|
|
|
) -> dict:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""更新产品分类
|
|
|
|
|
|
|
|
|
|
|
|
用途:修改已有产品分类的名称、状态等信息。
|
|
|
|
|
|
请求参数:category_id(路径参数)+ UpdateProductCategoryRequest(更新字段)。
|
|
|
|
|
|
返回值:更新后的分类信息。
|
|
|
|
|
|
权限要求:无特殊权限限制(未配置角色鉴权)。
|
|
|
|
|
|
"""
|
2026-05-14 23:28:09 +08:00
|
|
|
|
return success_payload(product_service.update_category(category_id, payload.model_dump(), session))
|
2026-05-14 13:51:06 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/products")
|
2026-05-14 23:28:09 +08:00
|
|
|
|
def list_products(
|
2026-05-30 07:23:33 +08:00
|
|
|
|
product_name: str | None = Query(default=None), # 产品名称模糊搜索
|
|
|
|
|
|
specification: str | None = Query(default=None), # 规格模糊搜索
|
|
|
|
|
|
category_id: int | None = Query(default=None), # 产品分类 ID 筛选
|
|
|
|
|
|
status: int | None = Query(default=None), # 产品状态筛选(启用/停用)
|
|
|
|
|
|
page_no: int = Query(default=1), # 页码,默认第 1 页
|
|
|
|
|
|
page_size: int = Query(default=20), # 每页条数,默认 20 条
|
|
|
|
|
|
product_service: ProductService = Depends(get_product_service), # 注入产品服务
|
|
|
|
|
|
session: Session = Depends(get_db_session), # 注入数据库会话
|
2026-05-14 23:28:09 +08:00
|
|
|
|
) -> dict:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""分页查询产品列表
|
|
|
|
|
|
|
|
|
|
|
|
用途:获取所有产品信息,支持按名称、规格、分类、状态筛选。
|
|
|
|
|
|
请求参数:Query 参数筛选 + 分页参数。
|
|
|
|
|
|
返回值:分页产品列表,包含 total、page_no、page_size、list。
|
|
|
|
|
|
权限要求:无特殊权限限制(未配置角色鉴权)。
|
|
|
|
|
|
"""
|
2026-05-14 23:28:09 +08:00
|
|
|
|
result = product_service.list_products(
|
|
|
|
|
|
{
|
|
|
|
|
|
"product_name": product_name,
|
|
|
|
|
|
"specification": specification,
|
|
|
|
|
|
"category_id": category_id,
|
|
|
|
|
|
"status": status,
|
|
|
|
|
|
},
|
|
|
|
|
|
session,
|
|
|
|
|
|
)
|
|
|
|
|
|
start = max(page_no - 1, 0) * page_size
|
|
|
|
|
|
result["list"] = result["list"][start : start + page_size]
|
|
|
|
|
|
result["page_no"] = page_no
|
|
|
|
|
|
result["page_size"] = page_size
|
|
|
|
|
|
return success_payload(result)
|
2026-05-14 13:51:06 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/api/products")
|
2026-05-14 23:28:09 +08:00
|
|
|
|
def create_product(
|
2026-05-30 07:23:33 +08:00
|
|
|
|
payload: CreateProductRequest, # 创建产品的请求体
|
|
|
|
|
|
product_service: ProductService = Depends(get_product_service), # 注入产品服务
|
|
|
|
|
|
session: Session = Depends(get_db_session), # 注入数据库会话
|
2026-05-14 23:28:09 +08:00
|
|
|
|
) -> dict:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""创建新产品
|
|
|
|
|
|
|
|
|
|
|
|
用途:新增一个产品记录。
|
|
|
|
|
|
请求参数:CreateProductRequest(产品名称、分类、规格等)。
|
|
|
|
|
|
返回值:创建成功后的产品信息。
|
|
|
|
|
|
权限要求:无特殊权限限制(未配置角色鉴权)。
|
|
|
|
|
|
"""
|
2026-05-14 23:28:09 +08:00
|
|
|
|
return success_payload(product_service.create_product(payload.model_dump(), session))
|
2026-05-14 13:51:06 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-05-21 14:59:38 +08:00
|
|
|
|
@router.put("/api/products/{product_id}")
|
|
|
|
|
|
def update_product(
|
2026-05-30 07:23:33 +08:00
|
|
|
|
product_id: int, # 产品 ID(路径参数)
|
|
|
|
|
|
payload: UpdateProductRequest, # 更新产品的请求体
|
|
|
|
|
|
product_service: ProductService = Depends(get_product_service), # 注入产品服务
|
|
|
|
|
|
session: Session = Depends(get_db_session), # 注入数据库会话
|
2026-05-21 14:59:38 +08:00
|
|
|
|
) -> dict:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""更新产品信息
|
|
|
|
|
|
|
|
|
|
|
|
用途:修改已有产品的基本信息。
|
|
|
|
|
|
请求参数:product_id(路径参数)+ UpdateProductRequest(更新字段)。
|
|
|
|
|
|
返回值:更新后的产品信息。
|
|
|
|
|
|
权限要求:无特殊权限限制(未配置角色鉴权)。
|
|
|
|
|
|
"""
|
2026-05-21 14:59:38 +08:00
|
|
|
|
return success_payload(product_service.update_product(product_id, payload.model_dump(), session))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/api/products/specifications/{product_id}")
|
|
|
|
|
|
def update_product_specification(
|
2026-05-30 07:23:33 +08:00
|
|
|
|
product_id: int, # 产品 ID(路径参数)
|
|
|
|
|
|
payload: UpdateProductSpecificationRequest, # 更新规格的请求体
|
|
|
|
|
|
product_service: ProductService = Depends(get_product_service), # 注入产品服务
|
|
|
|
|
|
session: Session = Depends(get_db_session), # 注入数据库会话
|
2026-05-21 14:59:38 +08:00
|
|
|
|
) -> dict:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""更新产品规格
|
|
|
|
|
|
|
|
|
|
|
|
用途:修改指定产品的规格信息(如尺寸、材质等)。
|
|
|
|
|
|
请求参数:product_id(路径参数)+ UpdateProductSpecificationRequest(规格数据)。
|
|
|
|
|
|
返回值:更新后的规格信息。
|
|
|
|
|
|
权限要求:无特殊权限限制(未配置角色鉴权)。
|
|
|
|
|
|
"""
|
2026-05-21 14:59:38 +08:00
|
|
|
|
return success_payload(product_service.update_product_specification(product_id, payload.model_dump(), session))
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-05 12:12:11 +08:00
|
|
|
|
@router.post("/api/products/{product_id}/specifications")
|
|
|
|
|
|
def add_product_specification(
|
|
|
|
|
|
product_id: int, # 产品组中任一规格的 ID
|
|
|
|
|
|
payload: UpdateProductSpecificationRequest, # 新增规格的请求体(复用更新规格的字段)
|
|
|
|
|
|
product_service: ProductService = Depends(get_product_service),
|
|
|
|
|
|
session: Session = Depends(get_db_session),
|
|
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""给已有产品追加规格
|
|
|
|
|
|
|
|
|
|
|
|
用途:在已有产品下新增一条规格明细。
|
|
|
|
|
|
请求参数:product_id(产品组中任一规格的 ID)+ 规格数据。
|
|
|
|
|
|
返回值:新增规格的信息。
|
|
|
|
|
|
"""
|
|
|
|
|
|
return success_payload(product_service.add_specification(product_id, payload.model_dump(), session))
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-21 14:59:38 +08:00
|
|
|
|
@router.put("/api/products/specifications/{product_id}/default")
|
|
|
|
|
|
def set_default_specification(
|
2026-05-30 07:23:33 +08:00
|
|
|
|
product_id: int, # 产品 ID(路径参数)
|
|
|
|
|
|
product_service: ProductService = Depends(get_product_service), # 注入产品服务
|
|
|
|
|
|
session: Session = Depends(get_db_session), # 注入数据库会话
|
2026-05-21 14:59:38 +08:00
|
|
|
|
) -> dict:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""设置默认规格
|
|
|
|
|
|
|
|
|
|
|
|
用途:将指定产品的某个规格设为默认规格,用于报价计算时的默认取值。
|
|
|
|
|
|
请求参数:product_id - 产品 ID(路径参数)。
|
|
|
|
|
|
返回值:设置结果。
|
|
|
|
|
|
权限要求:无特殊权限限制(未配置角色鉴权)。
|
|
|
|
|
|
"""
|
2026-05-21 14:59:38 +08:00
|
|
|
|
return success_payload(product_service.set_default_specification(product_id, session))
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-14 13:51:06 +08:00
|
|
|
|
@router.get("/api/products/{product_id}")
|
2026-05-14 23:28:09 +08:00
|
|
|
|
def get_product(
|
2026-05-30 07:23:33 +08:00
|
|
|
|
product_id: int, # 产品 ID(路径参数)
|
|
|
|
|
|
product_service: ProductService = Depends(get_product_service), # 注入产品服务
|
|
|
|
|
|
session: Session = Depends(get_db_session), # 注入数据库会话
|
2026-05-14 23:28:09 +08:00
|
|
|
|
) -> dict:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""查询产品详情
|
|
|
|
|
|
|
|
|
|
|
|
用途:根据产品 ID 获取单个产品的完整信息(含规格、分类等)。
|
|
|
|
|
|
请求参数:product_id - 产品 ID(路径参数)。
|
|
|
|
|
|
返回值:产品详情信息。
|
|
|
|
|
|
权限要求:无特殊权限限制(未配置角色鉴权)。
|
|
|
|
|
|
"""
|
2026-05-14 23:28:09 +08:00
|
|
|
|
return success_payload(product_service.get_product(product_id, session))
|
2026-06-02 20:45:00 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/api/product-categories/{category_id}")
|
|
|
|
|
|
def delete_category(
|
|
|
|
|
|
category_id: int,
|
|
|
|
|
|
session: Session = Depends(get_db_session),
|
2026-06-15 13:18:43 +08:00
|
|
|
|
current_user: dict = Depends(require_roles("manager", "admin")),
|
2026-06-02 20:45:00 +08:00
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""删除产品分类(软删除)"""
|
|
|
|
|
|
cat = session.get(ProductCategory, category_id)
|
|
|
|
|
|
if cat is None or cat.deleted == 1:
|
2026-06-15 13:18:43 +08:00
|
|
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="产品分类不存在", status_code=404)
|
2026-06-02 20:45:00 +08:00
|
|
|
|
cat.deleted = 1
|
|
|
|
|
|
session.commit()
|
2026-06-15 13:18:43 +08:00
|
|
|
|
cache_delete_pattern("product:*")
|
|
|
|
|
|
cache_delete_pattern("category:*")
|
2026-06-02 20:45:00 +08:00
|
|
|
|
return success_payload({"deleted": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/api/product-categories/{category_id}/permanent")
|
|
|
|
|
|
def permanent_delete_category(
|
|
|
|
|
|
category_id: int,
|
|
|
|
|
|
session: Session = Depends(get_db_session),
|
2026-06-15 13:18:43 +08:00
|
|
|
|
current_user: dict = Depends(require_roles("manager", "admin")),
|
2026-06-02 20:45:00 +08:00
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""永久删除产品分类(硬删除)"""
|
|
|
|
|
|
cat = session.get(ProductCategory, category_id)
|
|
|
|
|
|
if cat is None or cat.deleted == 1:
|
2026-06-15 13:18:43 +08:00
|
|
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="产品分类不存在", status_code=404)
|
2026-06-02 20:45:00 +08:00
|
|
|
|
count = session.execute(
|
|
|
|
|
|
select(func.count(Product.id)).where(
|
|
|
|
|
|
Product.category_id == category_id, Product.deleted == 0
|
|
|
|
|
|
)
|
|
|
|
|
|
).scalar()
|
|
|
|
|
|
if count > 0:
|
2026-06-15 13:18:43 +08:00
|
|
|
|
raise AppException(code=ErrorCode.BUSINESS_RULE_FAILED, message="该分类下还有产品,无法删除")
|
2026-06-02 20:45:00 +08:00
|
|
|
|
session.delete(cat)
|
|
|
|
|
|
session.commit()
|
2026-06-15 13:18:43 +08:00
|
|
|
|
cache_delete_pattern("product:*")
|
|
|
|
|
|
cache_delete_pattern("category:*")
|
2026-06-02 20:45:00 +08:00
|
|
|
|
return success_payload({"deleted": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/api/products/{product_id}")
|
|
|
|
|
|
def delete_product(
|
|
|
|
|
|
product_id: int,
|
|
|
|
|
|
session: Session = Depends(get_db_session),
|
2026-06-15 13:18:43 +08:00
|
|
|
|
current_user: dict = Depends(require_roles("manager", "admin")),
|
2026-06-02 20:45:00 +08:00
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""删除产品(软删除)"""
|
|
|
|
|
|
product = session.get(Product, product_id)
|
|
|
|
|
|
if product is None or product.deleted == 1:
|
2026-06-15 13:18:43 +08:00
|
|
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="产品不存在", status_code=404)
|
2026-06-02 20:45:00 +08:00
|
|
|
|
product.deleted = 1
|
|
|
|
|
|
session.commit()
|
2026-06-15 13:18:43 +08:00
|
|
|
|
cache_delete_pattern("product:*")
|
2026-06-02 20:45:00 +08:00
|
|
|
|
return success_payload({"deleted": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/api/products/{product_id}/permanent")
|
|
|
|
|
|
def permanent_delete_product(
|
|
|
|
|
|
product_id: int,
|
|
|
|
|
|
session: Session = Depends(get_db_session),
|
2026-06-15 13:18:43 +08:00
|
|
|
|
current_user: dict = Depends(require_roles("manager", "admin")),
|
2026-06-02 20:45:00 +08:00
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""永久删除产品(硬删除)"""
|
|
|
|
|
|
product = session.get(Product, product_id)
|
|
|
|
|
|
if product is None or product.deleted == 1:
|
2026-06-15 13:18:43 +08:00
|
|
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="产品不存在", status_code=404)
|
2026-06-02 20:45:00 +08:00
|
|
|
|
count = session.execute(
|
|
|
|
|
|
select(func.count(ProductPricingRule.id)).where(
|
|
|
|
|
|
ProductPricingRule.product_id == product_id
|
|
|
|
|
|
)
|
|
|
|
|
|
).scalar()
|
|
|
|
|
|
if count > 0:
|
2026-06-15 13:18:43 +08:00
|
|
|
|
raise AppException(code=ErrorCode.BUSINESS_RULE_FAILED, message="该产品还有定价规则,无法删除")
|
2026-06-02 20:45:00 +08:00
|
|
|
|
session.delete(product)
|
|
|
|
|
|
session.commit()
|
2026-06-15 13:18:43 +08:00
|
|
|
|
cache_delete_pattern("product:*")
|
2026-06-02 20:45:00 +08:00
|
|
|
|
return success_payload({"deleted": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/api/product-categories/batch-delete")
|
|
|
|
|
|
def batch_delete_categories(
|
|
|
|
|
|
ids: list[int] = Body(..., embed=True),
|
|
|
|
|
|
permanent: bool = Body(default=False, embed=True),
|
|
|
|
|
|
session: Session = Depends(get_db_session),
|
2026-06-15 13:18:43 +08:00
|
|
|
|
current_user: dict = Depends(require_roles("manager", "admin")),
|
2026-06-02 20:45:00 +08:00
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""批量删除产品分类"""
|
|
|
|
|
|
cats = session.execute(
|
|
|
|
|
|
select(ProductCategory).where(ProductCategory.id.in_(ids), ProductCategory.deleted == 0)
|
|
|
|
|
|
).scalars().all()
|
|
|
|
|
|
if not cats:
|
2026-06-15 13:18:43 +08:00
|
|
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="未找到可删除的分类", status_code=404)
|
2026-06-02 20:45:00 +08:00
|
|
|
|
if permanent:
|
|
|
|
|
|
cat_ids = [c.id for c in cats]
|
|
|
|
|
|
count = session.execute(
|
|
|
|
|
|
select(func.count(Product.id)).where(Product.category_id.in_(cat_ids), Product.deleted == 0)
|
|
|
|
|
|
).scalar()
|
|
|
|
|
|
if count > 0:
|
2026-06-15 13:18:43 +08:00
|
|
|
|
raise AppException(code=ErrorCode.BUSINESS_RULE_FAILED, message="所选分类下还有产品,无法删除")
|
2026-06-02 20:45:00 +08:00
|
|
|
|
for c in cats:
|
|
|
|
|
|
session.delete(c)
|
|
|
|
|
|
else:
|
|
|
|
|
|
for c in cats:
|
|
|
|
|
|
c.deleted = 1
|
|
|
|
|
|
session.commit()
|
2026-06-15 13:18:43 +08:00
|
|
|
|
cache_delete_pattern("product:*")
|
|
|
|
|
|
cache_delete_pattern("category:*")
|
2026-06-02 20:45:00 +08:00
|
|
|
|
return success_payload({"deleted": len(cats)})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/api/products/batch-delete")
|
|
|
|
|
|
def batch_delete_products(
|
|
|
|
|
|
ids: list[int] = Body(..., embed=True),
|
|
|
|
|
|
permanent: bool = Body(default=False, embed=True),
|
|
|
|
|
|
session: Session = Depends(get_db_session),
|
2026-06-15 13:18:43 +08:00
|
|
|
|
current_user: dict = Depends(require_roles("manager", "admin")),
|
2026-06-02 20:45:00 +08:00
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""批量删除产品"""
|
|
|
|
|
|
products = session.execute(
|
|
|
|
|
|
select(Product).where(Product.id.in_(ids), Product.deleted == 0)
|
|
|
|
|
|
).scalars().all()
|
|
|
|
|
|
if not products:
|
2026-06-15 13:18:43 +08:00
|
|
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="未找到可删除的产品", status_code=404)
|
2026-06-02 20:45:00 +08:00
|
|
|
|
if permanent:
|
|
|
|
|
|
prod_ids = [p.id for p in products]
|
|
|
|
|
|
count = session.execute(
|
|
|
|
|
|
select(func.count(ProductPricingRule.id)).where(ProductPricingRule.product_id.in_(prod_ids))
|
|
|
|
|
|
).scalar()
|
|
|
|
|
|
if count > 0:
|
2026-06-15 13:18:43 +08:00
|
|
|
|
raise AppException(code=ErrorCode.BUSINESS_RULE_FAILED, message="所选产品还有定价规则,无法删除")
|
2026-06-02 20:45:00 +08:00
|
|
|
|
for p in products:
|
|
|
|
|
|
session.delete(p)
|
|
|
|
|
|
else:
|
|
|
|
|
|
for p in products:
|
|
|
|
|
|
p.deleted = 1
|
|
|
|
|
|
session.commit()
|
2026-06-15 13:18:43 +08:00
|
|
|
|
cache_delete_pattern("product:*")
|
2026-06-02 20:45:00 +08:00
|
|
|
|
return success_payload({"deleted": len(products)})
|