Files
shanxia2.0/backend/app/crud/qc_material_usage.py
T

141 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""物料变动记录 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)