227 lines
7.4 KiB
Python
227 lines
7.4 KiB
Python
"""市场物料库 API"""
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.sample_reception import _get_current_user_id, _get_tenant_no, _page_payload
|
|
from app.core.database import get_db
|
|
from app.core.menu_guard import require_admin_or_super_admin, require_menu_code
|
|
from app.core.response import error_response, success_response
|
|
from app.crud import material as mat_svc
|
|
from app.crud import material_stock_log as msl_svc
|
|
|
|
router = APIRouter(prefix="/web/admin", tags=["市场物料"])
|
|
|
|
|
|
# ---------- Body models ----------
|
|
|
|
|
|
class MaterialBody(BaseModel):
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
material_code: str = Field(..., alias="materialCode")
|
|
material_name: str = Field(..., alias="materialName")
|
|
category: Optional[str] = None
|
|
spec_model: Optional[str] = Field(None, alias="specModel")
|
|
unit: Optional[str] = None
|
|
status: str = "1"
|
|
order_num: Optional[int] = Field(None, alias="orderNum")
|
|
remark: Optional[str] = None
|
|
image_urls: Optional[str] = Field(None, alias="imageUrls")
|
|
|
|
|
|
class MaterialUpdateBody(BaseModel):
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
material_code: Optional[str] = Field(None, alias="materialCode")
|
|
material_name: Optional[str] = Field(None, alias="materialName")
|
|
category: Optional[str] = None
|
|
spec_model: Optional[str] = Field(None, alias="specModel")
|
|
unit: Optional[str] = None
|
|
status: Optional[str] = None
|
|
order_num: Optional[int] = Field(None, alias="orderNum")
|
|
remark: Optional[str] = None
|
|
image_urls: Optional[str] = Field(None, alias="imageUrls")
|
|
|
|
|
|
class StockInBody(BaseModel):
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
qty: float = Field(..., gt=0)
|
|
remark: Optional[str] = None
|
|
|
|
|
|
class StockOutBody(BaseModel):
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
qty: float = Field(..., gt=0)
|
|
receiver: Optional[str] = None
|
|
purpose: Optional[str] = None
|
|
remark: Optional[str] = None
|
|
|
|
|
|
# ---------- 物料 CRUD ----------
|
|
|
|
|
|
@router.get("/materials")
|
|
async def list_materials(
|
|
page: int = Query(1, ge=1),
|
|
pageSize: int = Query(10, ge=1, le=100),
|
|
keyword: Optional[str] = None,
|
|
category: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_menu_code("biz:material:list")),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
items, total = mat_svc.list_materials(
|
|
db, tenant_no, page=page, page_size=pageSize,
|
|
keyword=keyword, category=category, status=status,
|
|
)
|
|
return success_response(_page_payload(items, total, page, pageSize))
|
|
except PermissionError as e:
|
|
return error_response(str(e), code=403)
|
|
except Exception as e:
|
|
return error_response(str(e))
|
|
|
|
|
|
@router.get("/materials/options")
|
|
async def material_options(
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_menu_code("biz:material:list")),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
return success_response(mat_svc.options_list(db, tenant_no=tenant_no))
|
|
except Exception as e:
|
|
return error_response(str(e))
|
|
|
|
|
|
@router.post("/materials")
|
|
async def create_material(
|
|
body: MaterialBody,
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_menu_code("biz:material:list")),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
user_id = _get_current_user_id(user)
|
|
obj = mat_svc.create_material(
|
|
db, body.model_dump(by_alias=True, exclude_none=True),
|
|
tenant_no=tenant_no, user_id=user_id,
|
|
)
|
|
return success_response(obj, message="创建成功")
|
|
except ValueError as e:
|
|
return error_response(str(e))
|
|
except Exception as e:
|
|
return error_response(str(e))
|
|
|
|
|
|
@router.put("/materials/{material_id}")
|
|
async def update_material(
|
|
material_id: str,
|
|
body: MaterialUpdateBody,
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_menu_code("biz:material:list")),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
user_id = _get_current_user_id(user)
|
|
obj = mat_svc.update_material(
|
|
db, material_id, body.model_dump(by_alias=True, exclude_none=True),
|
|
tenant_no=tenant_no, user_id=user_id,
|
|
)
|
|
return success_response(obj, message="更新成功")
|
|
except ValueError as e:
|
|
return error_response(str(e))
|
|
except Exception as e:
|
|
return error_response(str(e))
|
|
|
|
|
|
@router.delete("/materials/{material_id}")
|
|
async def delete_material(
|
|
material_id: str,
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_menu_code("biz:material:list")),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
user_id = _get_current_user_id(user)
|
|
mat_svc.soft_delete_material(db, material_id, tenant_no=tenant_no, user_id=user_id)
|
|
return success_response(None, message="删除成功")
|
|
except ValueError as e:
|
|
return error_response(str(e))
|
|
except Exception as e:
|
|
return error_response(str(e))
|
|
|
|
|
|
@router.post("/materials/{material_id}/stock-in")
|
|
async def stock_in_material(
|
|
material_id: str,
|
|
body: StockInBody,
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_menu_code("biz:material:list")),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
user_id = _get_current_user_id(user)
|
|
obj = mat_svc.stock_in(
|
|
db, material_id, qty=body.qty, remark=body.remark,
|
|
tenant_no=tenant_no, user_id=user_id,
|
|
)
|
|
return success_response(obj, message="入库成功")
|
|
except ValueError as e:
|
|
return error_response(str(e))
|
|
except Exception as e:
|
|
return error_response(str(e))
|
|
|
|
|
|
@router.post("/materials/{material_id}/stock-out")
|
|
async def stock_out_material(
|
|
material_id: str,
|
|
body: StockOutBody,
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_menu_code("biz:material:list")),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
user_id = _get_current_user_id(user)
|
|
obj = mat_svc.stock_out(
|
|
db, material_id, qty=body.qty, receiver=body.receiver,
|
|
purpose=body.purpose, remark=body.remark,
|
|
tenant_no=tenant_no, user_id=user_id,
|
|
)
|
|
return success_response(obj, message="出库成功")
|
|
except ValueError as e:
|
|
return error_response(str(e))
|
|
except Exception as e:
|
|
return error_response(str(e))
|
|
|
|
|
|
# ---------- 库存变动记录 ----------
|
|
|
|
|
|
@router.get("/material-stock-logs")
|
|
async def list_stock_logs(
|
|
page: int = Query(1, ge=1),
|
|
pageSize: int = Query(10, ge=1, le=100),
|
|
materialId: Optional[str] = None,
|
|
changeType: Optional[str] = None,
|
|
startTime: Optional[str] = None,
|
|
endTime: Optional[str] = None,
|
|
keyword: Optional[str] = None,
|
|
db: Session = Depends(get_db),
|
|
user: dict = Depends(require_menu_code("biz:materialStockLog:list")),
|
|
):
|
|
try:
|
|
tenant_no = _get_tenant_no(db, user)
|
|
items, total = msl_svc.list_stock_logs(
|
|
db, tenant_no, page=page, page_size=pageSize,
|
|
material_id=materialId, change_type=changeType,
|
|
start_time=startTime, end_time=endTime, keyword=keyword,
|
|
)
|
|
return success_response(_page_payload(items, total, page, pageSize))
|
|
except PermissionError as e:
|
|
return error_response(str(e), code=403)
|
|
except Exception as e:
|
|
return error_response(str(e))
|