feat(qc): implement material library with CRUD, stock-in, usage tracking and task deduction
This commit is contained in:
1 parent
4137c54588
commit
e6072457fd
9 files changed
+1638
-22
No files matched your search
@@ -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))
|
||||
Reference in new issue
Block a user