feat(qc): implement material library with CRUD, stock-in, usage tracking and task deduction

This commit is contained in:
zlwu committed 2026-05-20 06:36:50 +08:00
1 parent 4137c54588
commit e6072457fd
9 files changed
+1638 -22

No files matched your search

+177
View File
@@ -14,6 +14,8 @@ from app.crud import qc_task_result as result_svc
from app.crud import qc_task as task_svc
from app.crud import qc_third_party_company as tpc_svc
from app.crud import qc_third_party_item as tpi_svc
from app.crud import qc_material as mat_svc
from app.crud import qc_material_usage as mu_svc
from app.crud import qc_work_order as wo_svc
from app.crud.collection_protocol import total_pages
from app.api.sample_reception import _get_current_user_id, _get_tenant_no, _page_payload
@@ -373,6 +375,7 @@ class TaskCompleteWithResultBody(BaseModel):
results: List[Dict[str, Any]] = Field(default_factory=list, alias="results")
remark: Optional[str] = Field(None, alias="remark")
third_party_attachments: Optional[str] = Field(None, alias="thirdPartyAttachments")
material_usages: Optional[List[Dict[str, Any]]] = Field(None, alias="materialUsages")
@router.get("/qc-lab-test-items")
@@ -745,6 +748,18 @@ async def complete_task_with_result(
db, task_id, attachments=body.third_party_attachments,
tenant_no=tenant_no, user_id=user_id,
)
# 物料扣减(可选,仅当传了 materialUsages 时)
if body.material_usages:
task_obj_pre = task_svc.get_task(db, task_id, tenant_no=tenant_no)
task_no = task_obj_pre.task_no if task_obj_pre else ""
mu_svc.deduct_for_task(
db,
task_id,
task_no=task_no,
material_usages=body.material_usages,
tenant_no=tenant_no,
user_id=user_id,
)
obj = task_svc.apply_task_result(
db,
task_id,
@@ -788,3 +803,165 @@ async def update_qc_task_attachments(
return error_response(str(e))
except Exception as e:
return error_response(str(e))
# ---------- 质检物料库 ----------
class QcMaterialBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
material_code: str = Field(..., alias="materialCode")
material_name: str = Field(..., alias="materialName")
spec_model: Optional[str] = Field(None, alias="specModel")
unit: Optional[str] = None
warning_qty: Optional[float] = Field(0, alias="warningQty")
status: str = "1"
remark: Optional[str] = None
class QcMaterialUpdateBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
material_code: Optional[str] = Field(None, alias="materialCode")
material_name: Optional[str] = Field(None, alias="materialName")
spec_model: Optional[str] = Field(None, alias="specModel")
unit: Optional[str] = None
warning_qty: Optional[float] = Field(None, alias="warningQty")
status: Optional[str] = None
remark: Optional[str] = None
class StockInBody(BaseModel):
model_config = ConfigDict(populate_by_name=True)
qty: float = Field(..., gt=0)
remark: Optional[str] = None
@router.get("/qc-materials")
async def list_qc_materials(
page: int = Query(1, ge=1),
pageSize: int = Query(10, ge=1, le=100),
keyword: Optional[str] = None,
status: Optional[str] = None,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
):
try:
tenant_no = _get_tenant_no(db, user)
items, total = mat_svc.list_materials(
db, page=page, page_size=pageSize, keyword=keyword, status=status, tenant_no=tenant_no,
)
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("/qc-materials/options")
async def qc_material_options(
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcMaterial: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("/qc-materials")
async def create_qc_material(
body: QcMaterialBody,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcMaterial: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("/qc-materials/{material_id}")
async def update_qc_material(
material_id: str,
body: QcMaterialUpdateBody,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcMaterial: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("/qc-materials/{material_id}")
async def delete_qc_material(
material_id: str,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcMaterial: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("/qc-materials/{material_id}/stock-in")
async def stock_in_qc_material(
material_id: str,
body: StockInBody,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcMaterial: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.get("/qc-material-usages")
async def list_qc_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,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcMaterialUsage: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,
)
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))