"""库存查询业务服务层 将原 inventory_routes 中的业务编排(库存列表/建/改/删)下沉到此, 路由层只做参数校验与响应组装。 """ from typing import Optional from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from sqlalchemy.exc import IntegrityError from shared.models.identity import User from inventory.models import Product, Warehouse, Inventory from ..schemas import InventoryResponse, InventoryCreate, InventoryUpdate, PaginatedResponse def _build_inventory_response(inv: Inventory, product: Product, warehouse: Warehouse) -> InventoryResponse: return 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 ) class InventoryService: """库存查询业务服务""" @staticmethod async def list_inventory( db_session: AsyncSession, warehouse_id: Optional[int], product_id: Optional[int], low_stock: bool, skip: int, limit: int, ) -> PaginatedResponse: 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 = [_build_inventory_response(inv, product, warehouse) for inv, product, warehouse in result.all()] return PaginatedResponse(items=inventory_list, total=total, skip=skip, limit=limit) @staticmethod async def create_inventory( db_session: AsyncSession, payload: InventoryCreate, current_user: User, ) -> InventoryResponse: 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 _build_inventory_response(inventory, product, warehouse) @staticmethod async def update_inventory( db_session: AsyncSession, inventory_id: int, payload: InventoryUpdate, current_user: User, ) -> InventoryResponse: 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 _build_inventory_response(inventory, product, warehouse) @staticmethod async def delete_inventory( db_session: AsyncSession, inventory_id: int, current_user: User, ) -> dict: 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": "库存记录已删除"} inventory_service = InventoryService()