This commit is contained in:
2026-07-20 10:16:27 +08:00
parent 8493f0df03
commit f3d2951dc3
24 changed files with 23623 additions and 1546 deletions
+10 -157
View File
@@ -1,22 +1,17 @@
"""
库存管理路由模块
提供库存信息的查询功能,包括:
- 库存列表查询(支持分页、仓库筛选、产品筛选、低库存筛选)
- 显示产品库存数量、锁定数量、可用数量等信息
"""库存管理路由层 - 薄路由
业务逻辑下沉至 inventory.services.inventory_service,路由只做参数校验与响应组装。
路由前缀: /api/inventory
"""
from fastapi import APIRouter, Depends, Query, HTTPException
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update, func
from sqlalchemy.exc import IntegrityError
from typing import Optional, List
from typing import Optional
from shared.database.database import get_db_session
from shared.services.auth_service import get_current_active_user
from shared.models.database import User, Product, Warehouse, Inventory
from shared.models.database import User
from ..schemas import InventoryResponse, InventoryCreate, InventoryUpdate, PaginatedResponse
from ..services.inventory_service import inventory_service
router = APIRouter(prefix="/inventory", tags=["库存管理"])
@@ -31,43 +26,7 @@ async def list_inventory(
db_session: AsyncSession = Depends(get_db_session),
current_user: User = Depends(get_current_active_user)
):
base_query = (
select(Inventory, Product, Warehouse)
.join(Product, Inventory.product_id == Product.id)
.join(Warehouse, Inventory.warehouse_id == Warehouse.id)
.where(Product.is_active == True)
.where(Product.item_type == "material")
.where(Warehouse.is_active == True)
)
if warehouse_id:
base_query = base_query.where(Inventory.warehouse_id == warehouse_id)
if product_id:
base_query = base_query.where(Inventory.product_id == product_id)
if low_stock:
base_query = base_query.where(Inventory.quantity <= Product.min_stock)
count_query = select(func.count()).select_from(base_query.subquery())
total = await db_session.scalar(count_query) or 0
query = base_query.offset(skip).limit(limit)
result = await db_session.execute(query)
inventory_list = []
for inv, product, warehouse in result.all():
inventory_list.append(InventoryResponse(
id=inv.id,
product_id=inv.product_id,
product_name=product.name,
product_sku=product.sku,
warehouse_id=inv.warehouse_id,
warehouse_name=warehouse.name,
quantity=inv.quantity,
locked_quantity=inv.locked_quantity,
available_quantity=inv.available_quantity
))
return PaginatedResponse(items=inventory_list, total=total, skip=skip, limit=limit)
return await inventory_service.list_inventory(db_session, warehouse_id, product_id, low_stock, skip, limit)
@router.post("", response_model=InventoryResponse, status_code=201)
@@ -76,64 +35,7 @@ async def create_inventory(
db_session: AsyncSession = Depends(get_db_session),
current_user: User = Depends(get_current_active_user)
):
if payload.quantity < 0 or payload.locked_quantity < 0:
raise HTTPException(status_code=400, detail="库存数量不能为负数")
if payload.locked_quantity > payload.quantity:
raise HTTPException(status_code=400, detail="锁定数量不能大于库存数量")
product_result = await db_session.execute(
select(Product).where(Product.id == payload.product_id, Product.is_active == True)
)
product = product_result.scalar_one_or_none()
if not product:
raise HTTPException(status_code=404, detail="物料不存在")
if product.item_type != "material":
raise HTTPException(status_code=400, detail="库存仅支持物料")
warehouse_result = await db_session.execute(
select(Warehouse).where(Warehouse.id == payload.warehouse_id, Warehouse.is_active == True)
)
warehouse = warehouse_result.scalar_one_or_none()
if not warehouse:
raise HTTPException(status_code=404, detail="仓库不存在")
exists_result = await db_session.execute(
select(Inventory).where(
Inventory.product_id == payload.product_id,
Inventory.warehouse_id == payload.warehouse_id
)
)
if exists_result.scalar_one_or_none():
raise HTTPException(status_code=400, detail="该仓库已存在该物料库存记录")
inventory = Inventory(
product_id=payload.product_id,
warehouse_id=payload.warehouse_id,
quantity=payload.quantity,
locked_quantity=payload.locked_quantity,
batch_number=payload.batch_number,
location=payload.location
)
db_session.add(inventory)
try:
await db_session.commit()
except IntegrityError:
# 并发创建命中 (product_id, warehouse_id) 唯一约束
await db_session.rollback()
raise HTTPException(status_code=400, detail="该仓库已存在该物料库存记录")
await db_session.refresh(inventory)
return InventoryResponse(
id=inventory.id,
product_id=product.id,
product_name=product.name,
product_sku=product.sku,
warehouse_id=warehouse.id,
warehouse_name=warehouse.name,
quantity=inventory.quantity,
locked_quantity=inventory.locked_quantity,
available_quantity=inventory.available_quantity
)
return await inventory_service.create_inventory(db_session, payload, current_user)
@router.put("/{inventory_id}", response_model=InventoryResponse)
@@ -143,48 +45,7 @@ async def update_inventory(
db_session: AsyncSession = Depends(get_db_session),
current_user: User = Depends(get_current_active_user)
):
result = await db_session.execute(
select(Inventory, Product, Warehouse)
.join(Product, Inventory.product_id == Product.id)
.join(Warehouse, Inventory.warehouse_id == Warehouse.id)
.where(Inventory.id == inventory_id)
.where(Product.item_type == "material")
.with_for_update(of=Inventory)
)
row = result.first()
if not row:
raise HTTPException(status_code=404, detail="库存记录不存在")
inventory, product, warehouse = row
if payload.quantity is not None:
if payload.quantity < 0:
raise HTTPException(status_code=400, detail="库存数量不能为负数")
inventory.quantity = payload.quantity
if payload.locked_quantity is not None:
if payload.locked_quantity < 0:
raise HTTPException(status_code=400, detail="锁定数量不能为负数")
inventory.locked_quantity = payload.locked_quantity
if inventory.locked_quantity > inventory.quantity:
raise HTTPException(status_code=400, detail="锁定数量不能大于库存数量")
if payload.batch_number is not None:
inventory.batch_number = payload.batch_number
if payload.location is not None:
inventory.location = payload.location
await db_session.commit()
await db_session.refresh(inventory)
return InventoryResponse(
id=inventory.id,
product_id=product.id,
product_name=product.name,
product_sku=product.sku,
warehouse_id=warehouse.id,
warehouse_name=warehouse.name,
quantity=inventory.quantity,
locked_quantity=inventory.locked_quantity,
available_quantity=inventory.available_quantity
)
return await inventory_service.update_inventory(db_session, inventory_id, payload, current_user)
@router.delete("/{inventory_id}")
@@ -193,12 +54,4 @@ async def delete_inventory(
db_session: AsyncSession = Depends(get_db_session),
current_user: User = Depends(get_current_active_user)
):
result = await db_session.execute(select(Inventory).where(Inventory.id == inventory_id))
inventory = result.scalar_one_or_none()
if not inventory:
raise HTTPException(status_code=404, detail="库存记录不存在")
if inventory.quantity > 0:
raise HTTPException(status_code=400, detail="库存数量不为零,无法删除库存记录")
await db_session.delete(inventory)
await db_session.commit()
return {"message": "库存记录已删除"}
return await inventory_service.delete_inventory(db_session, inventory_id, current_user)