141 lines
4.7 KiB
Python
141 lines
4.7 KiB
Python
"""物料变动记录 CRUD"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from decimal import Decimal
|
||
from typing import Any, Dict, List, Optional, Tuple
|
||
|
||
from fastapi import HTTPException
|
||
from sqlalchemy import func
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.utils import generate_uuid
|
||
from app.models.qc_material import BizQcMaterial, BizQcMaterialUsage
|
||
|
||
|
||
def _row_to_dict(r: BizQcMaterialUsage) -> Dict[str, Any]:
|
||
return {
|
||
"id": r.id,
|
||
"materialId": r.material_id,
|
||
"materialCode": r.material_code or "",
|
||
"materialName": r.material_name or "",
|
||
"changeType": r.change_type,
|
||
"changeQty": float(r.change_qty) if r.change_qty is not None else 0,
|
||
"stockAfter": float(r.stock_after) if r.stock_after is not None else 0,
|
||
"qcTaskId": r.qc_task_id or "",
|
||
"qcTaskNo": r.qc_task_no or "",
|
||
"sourceType": r.source_type,
|
||
"remark": r.remark or "",
|
||
"createBy": r.create_by or "",
|
||
"createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "",
|
||
"updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "",
|
||
}
|
||
|
||
|
||
def list_usages(
|
||
db: Session,
|
||
tenant_no: str,
|
||
*,
|
||
page: int = 1,
|
||
page_size: int = 10,
|
||
material_id: Optional[str] = None,
|
||
change_type: Optional[str] = None,
|
||
start_time: Optional[str] = None,
|
||
end_time: Optional[str] = None,
|
||
) -> Tuple[List[Dict[str, Any]], int]:
|
||
def _filtered():
|
||
q0 = db.query(BizQcMaterialUsage).filter(
|
||
BizQcMaterialUsage.tenant_no == tenant_no,
|
||
BizQcMaterialUsage.del_flag == 0,
|
||
)
|
||
if material_id:
|
||
q0 = q0.filter(BizQcMaterialUsage.material_id == material_id)
|
||
if change_type:
|
||
q0 = q0.filter(BizQcMaterialUsage.change_type == change_type)
|
||
if start_time:
|
||
q0 = q0.filter(BizQcMaterialUsage.create_time >= start_time)
|
||
if end_time:
|
||
q0 = q0.filter(BizQcMaterialUsage.create_time <= end_time + " 23:59:59")
|
||
return q0
|
||
|
||
total = _filtered().order_by(None).with_entities(func.count(BizQcMaterialUsage.id)).scalar() or 0
|
||
page = max(1, page)
|
||
page_size = min(max(1, page_size), 100)
|
||
rows = (
|
||
_filtered()
|
||
.order_by(BizQcMaterialUsage.create_time.desc())
|
||
.offset((page - 1) * page_size)
|
||
.limit(page_size)
|
||
.all()
|
||
)
|
||
return [_row_to_dict(x) for x in rows], int(total)
|
||
|
||
|
||
def deduct_for_task(
|
||
db: Session,
|
||
task_id: str,
|
||
task_no: str,
|
||
material_usages: List[Dict[str, Any]],
|
||
tenant_no: str,
|
||
user_id: Optional[str] = None,
|
||
dept_id: str = "",
|
||
) -> None:
|
||
"""扣减物料库存并生成变动记录。调用方控制事务,本函数不 commit。"""
|
||
for item in material_usages:
|
||
mid = item.get("materialId") or ""
|
||
qty = item.get("qty")
|
||
if not mid:
|
||
raise HTTPException(status_code=400, detail="物料ID不能为空")
|
||
if qty is None or Decimal(str(qty)) <= 0:
|
||
raise HTTPException(status_code=400, detail="物料使用数量必须大于0")
|
||
|
||
qty_dec = Decimal(str(qty))
|
||
|
||
material = (
|
||
db.query(BizQcMaterial)
|
||
.with_for_update()
|
||
.filter(
|
||
BizQcMaterial.id == mid,
|
||
BizQcMaterial.tenant_no == tenant_no,
|
||
BizQcMaterial.del_flag == 0,
|
||
BizQcMaterial.status == "1",
|
||
)
|
||
.first()
|
||
)
|
||
if not material:
|
||
raise HTTPException(status_code=400, detail=f"物料不存在或已停用(ID: {mid})")
|
||
|
||
stock_qty = material.stock_qty if material.stock_qty is not None else Decimal("0")
|
||
if stock_qty < qty_dec:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail=f"物料「{material.material_name}」库存不足,当前库存 {stock_qty},需使用 {qty_dec}",
|
||
)
|
||
|
||
new_stock = stock_qty - qty_dec
|
||
material.stock_qty = new_stock
|
||
material.update_by = user_id
|
||
material.update_time = datetime.now()
|
||
|
||
usage = BizQcMaterialUsage(
|
||
id=generate_uuid(),
|
||
material_id=mid,
|
||
material_code=material.material_code,
|
||
material_name=material.material_name,
|
||
change_type="use",
|
||
change_qty=-qty_dec,
|
||
stock_after=new_stock,
|
||
qc_task_id=task_id,
|
||
qc_task_no=task_no,
|
||
source_type="task_complete",
|
||
tenant_no=tenant_no,
|
||
remark="",
|
||
create_by=user_id,
|
||
create_dept_id=dept_id,
|
||
create_time=datetime.now(),
|
||
update_by=user_id,
|
||
update_time=datetime.now(),
|
||
del_flag=0,
|
||
)
|
||
db.add(usage)
|