dingdanquanliucheng/backend/app/api/suppliers.py
taiyi 1a3e9b0197 feat: 为 6 个后台管理模块添加删除和批量删除功能
后端新增 18 个 DELETE 端点(软删除/硬删除/批量删除),
覆盖产品分类、产品、客户、供应商、用户、角色。
硬删除带前置检查,有关联数据时拒绝删除。
前端每个表格新增全选复选框和批量删除按钮。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 20:45:00 +08:00

200 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
供应商管理路由模块
职责:
处理供应商信息的增删改查接口URL 前缀为 /api/suppliers。
包括:供应商列表查询、创建供应商、查看详情、更新供应商信息、删除供应商。
"""
from fastapi import APIRouter, Body, Depends, Query
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from backend.app.api.deps import get_supplier_service, require_roles
from backend.app.core.error_codes import ErrorCode
from backend.app.core.exceptions import AppException
from backend.app.db import get_db_session
from backend.app.models.business import Supplier, SalesOrder
from backend.app.schemas.common import success_payload
from backend.app.schemas.suppliers import CreateSupplierRequest, UpdateSupplierRequest
from backend.app.services.supplier_service import SupplierService
router = APIRouter(prefix="/api/suppliers", tags=["suppliers"])
@router.get("")
def list_suppliers(
supplier_name: str | None = Query(default=None), # 供应商名称模糊搜索
supplier_type: 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 条
supplier_service: SupplierService = Depends(get_supplier_service), # 注入供应商服务
session: Session = Depends(get_db_session), # 注入数据库会话
current_user: dict = Depends(require_roles("salesman", "manager", "admin")), # 角色鉴权
) -> dict:
"""分页查询供应商列表
用途:获取所有供应商信息,支持按名称、类型、状态筛选。
请求参数Query 参数筛选 + 分页参数。
返回值:分页供应商列表,包含 total、page_no、page_size、list。
权限要求业务员salesman、经理manager、管理员admin
"""
result = supplier_service.list_suppliers(
{"supplier_name": supplier_name, "supplier_type": supplier_type, "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)
@router.post("")
def create_supplier(
payload: CreateSupplierRequest, # 创建供应商的请求体
supplier_service: SupplierService = Depends(get_supplier_service), # 注入供应商服务
session: Session = Depends(get_db_session), # 注入数据库会话
current_user: dict = Depends(require_roles("manager", "admin")), # 角色鉴权:仅经理/管理员
) -> dict:
"""创建新供应商
用途:新增一个供应商记录。
请求参数CreateSupplierRequest供应商名称、类型、联系方式等
返回值:创建成功后的供应商信息。
权限要求经理manager或管理员admin
"""
return success_payload(supplier_service.create_supplier(payload.model_dump(), session))
@router.get("/{supplier_id}")
def get_supplier(
supplier_id: int, # 供应商 ID路径参数
supplier_service: SupplierService = Depends(get_supplier_service), # 注入供应商服务
session: Session = Depends(get_db_session), # 注入数据库会话
current_user: dict = Depends(require_roles("salesman", "manager", "admin")), # 角色鉴权
) -> dict:
"""查询供应商详情
用途:根据供应商 ID 获取单个供应商的详细信息。
请求参数supplier_id - 供应商 ID路径参数
返回值:供应商详情信息。
权限要求业务员salesman、经理manager、管理员admin
"""
supplier = supplier_service.get_supplier(supplier_id, session)
if not supplier:
raise AppException(code=ErrorCode.NOT_FOUND, message="供应商不存在", status_code=404)
return success_payload(supplier)
@router.put("/{supplier_id}")
def update_supplier(
supplier_id: int, # 供应商 ID路径参数
payload: UpdateSupplierRequest, # 更新供应商的请求体
supplier_service: SupplierService = Depends(get_supplier_service), # 注入供应商服务
session: Session = Depends(get_db_session), # 注入数据库会话
current_user: dict = Depends(require_roles("manager", "admin")), # 角色鉴权:仅经理/管理员
) -> dict:
"""更新供应商信息
用途:修改已有供应商的基本信息。
请求参数supplier_id路径参数+ UpdateSupplierRequest更新字段
返回值:更新后的供应商信息。
权限要求经理manager或管理员admin
"""
result = supplier_service.update_supplier(supplier_id, payload.model_dump(), session)
if not result:
raise AppException(code=ErrorCode.NOT_FOUND, message="供应商不存在", status_code=404)
return success_payload(result)
@router.delete("/{supplier_id}")
def delete_supplier(
supplier_id: int, # 供应商 ID路径参数
supplier_service: SupplierService = Depends(get_supplier_service), # 注入供应商服务
session: Session = Depends(get_db_session), # 注入数据库会话
current_user: dict = Depends(require_roles("manager", "admin")), # 角色鉴权:仅经理/管理员
) -> dict:
"""软删除供应商
用途将供应商标记为已删除deleted=1不实际删除数据。
请求参数supplier_id - 供应商 ID路径参数
返回值:删除结果 {"deleted": true}。
权限要求经理manager或管理员admin
"""
supplier = session.execute(
select(Supplier).where(Supplier.id == supplier_id, Supplier.deleted == 0)
).scalar_one_or_none()
if not supplier:
raise AppException(code=ErrorCode.NOT_FOUND, message="供应商不存在", status_code=404)
supplier.deleted = 1
session.commit()
return success_payload({"deleted": True})
@router.delete("/{supplier_id}/permanent")
def permanent_delete_supplier(
supplier_id: int, # 供应商 ID路径参数
supplier_service: SupplierService = Depends(get_supplier_service), # 注入供应商服务
session: Session = Depends(get_db_session), # 注入数据库会话
current_user: dict = Depends(require_roles("manager", "admin")), # 角色鉴权:仅经理/管理员
) -> dict:
"""永久删除供应商
用途:彻底删除供应商记录。若存在关联的有效订单则禁止删除。
请求参数supplier_id - 供应商 ID路径参数
返回值:删除结果 {"deleted": true}。
权限要求经理manager或管理员admin
"""
supplier = session.execute(
select(Supplier).where(Supplier.id == supplier_id, Supplier.deleted == 0)
).scalar_one_or_none()
if not supplier:
raise AppException(code=ErrorCode.NOT_FOUND, message="供应商不存在", status_code=404)
order_count = session.execute(
select(func.count(SalesOrder.id)).where(
SalesOrder.factory_id == supplier_id,
SalesOrder.order_status != "cancelled",
)
).scalar()
if order_count > 0:
raise AppException(
code=ErrorCode.PARAM_ERROR,
message="该供应商还有关联订单,无法删除",
status_code=400,
)
session.delete(supplier)
session.commit()
return success_payload({"deleted": True})
@router.post("/batch-delete")
def batch_delete_suppliers(
ids: list[int] = Body(..., embed=True),
permanent: bool = Body(default=False, embed=True),
session: Session = Depends(get_db_session),
current_user: dict = Depends(require_roles("manager", "admin")),
) -> dict:
"""批量删除供应商"""
suppliers = session.execute(
select(Supplier).where(Supplier.id.in_(ids), Supplier.deleted == 0)
).scalars().all()
if not suppliers:
raise AppException(code=ErrorCode.NOT_FOUND, message="未找到可删除的供应商", status_code=404)
if permanent:
sup_ids = [s.id for s in suppliers]
count = session.execute(
select(func.count(SalesOrder.id)).where(
SalesOrder.factory_id.in_(sup_ids), SalesOrder.order_status != "cancelled"
)
).scalar()
if count > 0:
raise AppException(code=ErrorCode.PARAM_ERROR, message="所选供应商还有关联订单,无法删除", status_code=400)
for s in suppliers:
session.delete(s)
else:
for s in suppliers:
s.deleted = 1
session.commit()
return success_payload({"deleted": len(suppliers)})