| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844 |
- """市场物料库 API"""
- from datetime import datetime
- from decimal import Decimal
- from typing import List, 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.utils import parse_datetime
- from app.core.menu_guard import require_any_menu_code, 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_batch as batch_svc
- from app.crud import material_stock_check as stock_check_svc
- from app.crud import material_usage as mu_svc
- from app.crud import material_stock_log as msl_svc
- from app.models.models import SysUser
- router = APIRouter(prefix="/web/admin", tags=["市场物料"])
- def _resolve_operator_name(db: Session, user: dict, operator_name: Optional[str]) -> str:
- name = (operator_name or "").strip()
- if name:
- return name
- user_id = _get_current_user_id(user)
- if user_id:
- row = (
- db.query(SysUser)
- .filter(SysUser.id == user_id, SysUser.del_flag == 0)
- .first()
- )
- if row:
- return (row.nick_name or row.user_no or "").strip()
- return (user.get("real_name") or user.get("username") or "").strip()
- # ---------- 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
- warning_qty: Optional[float] = Field(0, alias="warningQty")
- 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
- warning_qty: Optional[float] = Field(None, alias="warningQty")
- 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)
- stock_in_no: Optional[str] = Field(None, alias="stockInNo")
- stock_in_date: Optional[str] = Field(None, alias="stockInDate")
- expire_date: Optional[str] = Field(None, alias="expireDate")
- catalog_no: Optional[str] = Field(None, alias="catalogNo")
- batch_no: Optional[str] = Field(None, alias="batchNo")
- manufacturer: Optional[str] = None
- unit_price: Optional[float] = Field(None, alias="unitPrice")
- amount: Optional[float] = None
- qty: Optional[float] = Field(None, ge=0)
- remark: Optional[str] = None
- stock_in_time: Optional[str] = Field(None, alias="stockInTime")
- operator_name: Optional[str] = Field(None, alias="operatorName")
- class BatchStockInItem(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- material_id: str = Field(..., alias="materialId")
- stock_in_no: Optional[str] = Field(None, alias="stockInNo")
- stock_in_date: Optional[str] = Field(None, alias="stockInDate")
- expire_date: Optional[str] = Field(None, alias="expireDate")
- catalog_no: Optional[str] = Field(None, alias="catalogNo")
- batch_no: Optional[str] = Field(None, alias="batchNo")
- manufacturer: Optional[str] = None
- unit_price: Optional[float] = Field(None, alias="unitPrice")
- amount: Optional[float] = None
- qty: Optional[float] = Field(None, ge=0)
- remark: Optional[str] = None
- class BatchStockInBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- items: List[BatchStockInItem] = Field(..., min_length=1)
- remark: Optional[str] = None
- stock_in_time: Optional[str] = Field(None, alias="stockInTime")
- operator_name: Optional[str] = Field(None, alias="operatorName")
- class BatchStockOutItem(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- batch_id: str = Field(..., alias="batchId")
- qty: float = Field(..., gt=0)
- remark: Optional[str] = None
- class BatchStockOutBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- items: List[BatchStockOutItem] = Field(..., min_length=1)
- receiver: Optional[str] = None
- purpose: Optional[str] = None
- remark: Optional[str] = None
- stock_out_time: Optional[str] = Field(None, alias="stockOutTime")
- operator_name: Optional[str] = Field(None, alias="operatorName")
- class BatchDiscardItem(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- batch_id: str = Field(..., alias="batchId")
- qty: float = Field(..., gt=0)
- usage_unit: Optional[str] = Field(None, alias="usageUnit")
- remark: Optional[str] = None
- class BatchDiscardBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- items: List[BatchDiscardItem] = Field(..., min_length=1)
- discard_reason: str = Field(..., alias="discardReason")
- remark: Optional[str] = None
- discard_time: Optional[str] = Field(None, alias="discardTime")
- operator_name: Optional[str] = Field(None, alias="operatorName")
- class MaterialBatchUpdateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- stock_in_no: Optional[str] = Field(None, alias="stockInNo")
- stock_in_date: Optional[str] = Field(None, alias="stockInDate")
- expire_date: Optional[str] = Field(None, alias="expireDate")
- catalog_no: Optional[str] = Field(None, alias="catalogNo")
- batch_no: Optional[str] = Field(None, alias="batchNo")
- manufacturer: Optional[str] = None
- unit_price: Optional[float] = Field(None, alias="unitPrice")
- amount: Optional[float] = None
- remark: Optional[str] = None
- 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
- warning_qty: Optional[float] = Field(None, alias="warningQty")
- status: Optional[str] = None
- class StockCheckCreateBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- title: str
- check_time: Optional[str] = Field(None, alias="checkTime")
- remark: Optional[str] = None
- class StockCheckUnusedLineItem(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- usage_id: Optional[str] = Field(None, alias="usageId")
- batch_id: Optional[str] = Field(None, alias="batchId")
- material_code: Optional[str] = Field(None, alias="materialCode")
- material_name: str = Field(..., alias="materialName")
- spec_model: Optional[str] = Field(None, alias="specModel")
- unit: Optional[str] = None
- stock_in_no: Optional[str] = Field(None, alias="stockInNo")
- stock_in_date: Optional[str] = Field(None, alias="stockInDate")
- expire_date: Optional[str] = Field(None, alias="expireDate")
- catalog_no: Optional[str] = Field(None, alias="catalogNo")
- batch_no: Optional[str] = Field(None, alias="batchNo")
- manufacturer: Optional[str] = None
- unit_price: Optional[float] = Field(None, alias="unitPrice")
- holder: Optional[str] = None
- unused_qty: float = Field(..., alias="unusedQty", gt=0)
- remark: Optional[str] = None
- class StockCheckUnusedLinesBody(BaseModel):
- model_config = ConfigDict(populate_by_name=True)
- items: List[StockCheckUnusedLineItem] = Field(default_factory=list)
- # ---------- 入库批次 ----------
- @router.get("/material-batches")
- async def list_material_batches(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- materialId: 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 = batch_svc.list_batches(
- db,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- material_id=materialId,
- category=category,
- status=status,
- tenant_no=tenant_no,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/material-batches/options")
- async def material_batch_options(
- keyword: Optional[str] = None,
- materialId: Optional[str] = None,
- onlyAvailable: bool = Query(True, alias="onlyAvailable"),
- 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(
- batch_svc.batch_options(
- db,
- tenant_no,
- keyword=keyword,
- material_id=materialId,
- only_available=onlyAvailable,
- )
- )
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/material-batches/{batch_id}")
- async def update_material_batch(
- batch_id: str,
- body: MaterialBatchUpdateBody,
- 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)
- data = body.model_dump(exclude_none=True)
- obj = batch_svc.update_batch(
- db,
- batch_id,
- tenant_no=tenant_no,
- user_id=user_id,
- **data,
- )
- material = mat_svc.get_material_entity(db, obj.material_id, tenant_no)
- return success_response(
- batch_svc._batch_row_to_dict(obj, material),
- message="更新成功",
- )
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.delete("/material-batches/{batch_id}")
- async def delete_material_batch(
- batch_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)
- batch_svc.delete_batch(db, batch_id, tenant_no=tenant_no, user_id=user_id)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 物料 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, user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, 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(400, 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(400, str(e))
- except Exception as e:
- return error_response(400, 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(400, str(e))
- except Exception as e:
- return error_response(400, 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(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/materials/batch-stock-in")
- async def batch_stock_in_material(
- body: BatchStockInBody,
- 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)
- operate_time = datetime.now()
- if body.stock_in_time:
- rt = parse_datetime(body.stock_in_time)
- if not rt:
- return error_response(400, "入库时间格式无效")
- operate_time = rt
- items = [
- {
- "material_id": x.material_id,
- "materialId": x.material_id,
- "stock_in_no": x.stock_in_no,
- "stockInNo": x.stock_in_no,
- "stock_in_date": x.stock_in_date,
- "stockInDate": x.stock_in_date,
- "expire_date": x.expire_date,
- "expireDate": x.expire_date,
- "catalog_no": x.catalog_no,
- "catalogNo": x.catalog_no,
- "batch_no": x.batch_no,
- "batchNo": x.batch_no,
- "manufacturer": x.manufacturer,
- "unit_price": x.unit_price,
- "unitPrice": x.unit_price,
- "amount": x.amount,
- "qty": x.qty,
- "remark": x.remark,
- }
- for x in body.items
- ]
- count = batch_svc.batch_stock_in(
- db,
- items=items,
- remark=body.remark,
- operator_name=_resolve_operator_name(db, user, body.operator_name),
- tenant_no=tenant_no,
- user_id=user_id,
- operate_time=operate_time,
- )
- return success_response({"count": count}, message=f"批量入库成功,共 {count} 项")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/materials/batch-stock-out")
- async def batch_stock_out_material(
- body: BatchStockOutBody,
- 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)
- operate_time = datetime.now()
- if body.stock_out_time:
- rt = parse_datetime(body.stock_out_time)
- if not rt:
- return error_response(400, "出库时间格式无效")
- operate_time = rt
- items = [
- {"batch_id": x.batch_id, "batchId": x.batch_id, "qty": x.qty, "remark": x.remark}
- for x in body.items
- ]
- count = batch_svc.batch_stock_out(
- db,
- items=items,
- receiver=body.receiver,
- purpose=body.purpose,
- remark=body.remark,
- operator_name=_resolve_operator_name(db, user, body.operator_name),
- tenant_no=tenant_no,
- user_id=user_id,
- operate_time=operate_time,
- )
- return success_response({"count": count}, message=f"批量出库成功,共 {count} 项")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/materials/batch-discard")
- async def batch_discard_material(
- body: BatchDiscardBody,
- 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)
- operate_time = datetime.now()
- if body.discard_time:
- rt = parse_datetime(body.discard_time)
- if not rt:
- return error_response(400, "废弃时间格式无效")
- operate_time = rt
- items = [
- {
- "batch_id": x.batch_id,
- "batchId": x.batch_id,
- "qty": x.qty,
- "usageUnit": x.usage_unit,
- "usage_unit": x.usage_unit,
- "remark": x.remark,
- }
- for x in body.items
- ]
- count = batch_svc.batch_discard(
- db,
- items=items,
- discard_reason=body.discard_reason,
- remark=body.remark,
- operator_name=_resolve_operator_name(db, user, body.operator_name),
- tenant_no=tenant_no,
- user_id=user_id,
- operate_time=operate_time,
- )
- return success_response({"count": count}, message=f"批量废弃成功,共 {count} 项")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, 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)
- operate_time = datetime.now()
- if body.stock_in_time:
- rt = parse_datetime(body.stock_in_time)
- if not rt:
- return error_response(400, "入库时间格式无效")
- operate_time = rt
- obj = batch_svc.stock_in_one(
- db,
- material_id,
- stock_in_no=body.stock_in_no,
- stock_in_date=body.stock_in_date,
- expire_date=body.expire_date,
- catalog_no=body.catalog_no,
- batch_no=body.batch_no,
- manufacturer=body.manufacturer,
- unit_price=body.unit_price,
- amount=body.amount,
- qty=body.qty,
- remark=body.remark,
- operator_name=_resolve_operator_name(db, user, body.operator_name),
- tenant_no=tenant_no,
- user_id=user_id,
- operate_time=operate_time,
- )
- return success_response(batch_svc._batch_row_to_dict(obj), message="入库成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/material-batches/batch-stock-in")
- async def batch_stock_in_material_batches(
- body: BatchStockInBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:material:list")),
- ):
- return await batch_stock_in_material(body, db, user)
- @router.post("/material-batches/batch-stock-out")
- async def batch_stock_out_material_batches(
- body: BatchStockOutBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:material:list")),
- ):
- return await batch_stock_out_material(body, db, user)
- @router.post("/material-batches/batch-discard")
- async def batch_discard_material_batches(
- body: BatchDiscardBody,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:material:list")),
- ):
- return await batch_discard_material(body, db, user)
- # ---------- 物料变动记录 ----------
- @router.get("/material-usages")
- async def list_material_usages(
- 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,
- stockInNo: 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 = mu_svc.list_usages(
- db, tenant_no, page=page, page_size=pageSize,
- material_id=materialId, change_type=changeType,
- start_time=startTime, end_time=endTime,
- keyword=keyword, stock_in_no=stockInNo,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, 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")),
- ):
- """历史库存变动(biz_material_stock_log,只读)。"""
- 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, user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/material-usages/{usage_id}/mark-fully-used")
- async def mark_material_usage_fully_used(
- usage_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(
- require_any_menu_code("biz:material:list", "market:material-usage:list")
- ),
- ):
- """标记领用记录已使用完(盘点登记 / 变动记录)。"""
- try:
- tenant_no = _get_tenant_no(db, user)
- user_id = _get_current_user_id(user)
- mu_svc.mark_requisition_fully_used(
- db,
- usage_id,
- tenant_no=tenant_no,
- user_id=user_id,
- user_info=user,
- )
- return success_response(None, message="已标记为使用完")
- except PermissionError as e:
- return error_response(403, str(e))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- # ---------- 库存盘点 ----------
- @router.get("/material-stock-check-requisitions")
- async def list_material_stock_check_requisitions(
- page: int = Query(1, ge=1),
- pageSize: int = Query(50, ge=1, le=200),
- keyword: 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 = mu_svc.list_stock_check_requisitions(
- db,
- tenant_no,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/material-stock-checks")
- async def list_material_stock_checks(
- page: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- keyword: Optional[str] = None,
- status: Optional[str] = None,
- startTime: Optional[str] = None,
- endTime: 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 = stock_check_svc.list_checks(
- db,
- tenant_no,
- page=page,
- page_size=pageSize,
- keyword=keyword,
- status=status,
- start_time=startTime,
- end_time=endTime,
- user_info=user,
- )
- return success_response(_page_payload(items, total, page, pageSize))
- except PermissionError as e:
- return error_response(403, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/material-stock-checks")
- async def create_material_stock_check(
- body: StockCheckCreateBody,
- 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)
- dept_id = user.get("deptId") or user.get("dept_id") or ""
- res = stock_check_svc.create_check(
- db,
- title=body.title,
- tenant_no=tenant_no,
- user_id=user_id,
- dept_id=dept_id,
- check_time=body.check_time,
- remark=body.remark,
- )
- return success_response(res, message="盘点单创建成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.get("/material-stock-checks/{check_id}")
- async def get_material_stock_check(
- check_id: str,
- db: Session = Depends(get_db),
- user: dict = Depends(require_menu_code("biz:material:list")),
- ):
- try:
- tenant_no = _get_tenant_no(db, user)
- data = stock_check_svc.get_check_detail(db, check_id, tenant_no)
- return success_response(data)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.put("/material-stock-checks/{check_id}/unused-lines")
- async def save_material_stock_check_unused_lines(
- check_id: str,
- body: StockCheckUnusedLinesBody,
- 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)
- dept_id = user.get("deptId") or user.get("dept_id") or ""
- count = stock_check_svc.save_unused_lines(
- db,
- check_id,
- items=[x.model_dump(by_alias=False) for x in body.items],
- tenant_no=tenant_no,
- user_id=user_id,
- dept_id=dept_id,
- )
- return success_response({"count": count}, message="登记保存成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/material-stock-checks/{check_id}/complete")
- async def complete_material_stock_check(
- check_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)
- stock_check_svc.complete_check(db, check_id, tenant_no=tenant_no, user_id=user_id)
- return success_response(None, message="盘点已完成")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
- @router.post("/material-stock-checks/{check_id}/cancel")
- async def cancel_material_stock_check(
- check_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)
- stock_check_svc.cancel_check(db, check_id, tenant_no=tenant_no, user_id=user_id)
- return success_response(None, message="盘点已作废")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(400, str(e))
|