diff --git a/backend/app/crud/material.py b/backend/app/crud/material.py index ec2fd350..68a972da 100644 --- a/backend/app/crud/material.py +++ b/backend/app/crud/material.py @@ -9,7 +9,7 @@ from sqlalchemy import func, or_ from sqlalchemy.orm import Session from app.core.utils import generate_uuid -from app.models.material import BizMaterial +from app.models.material import BizMaterial, BizMaterialStockBatch, BizMaterialUsage def _dec(v: Any) -> float: @@ -362,3 +362,153 @@ def options_list(db: Session, tenant_no: str) -> List[Dict[str, Any]]: } for r in rows ] + + +def _my_usage_row_to_dict( + r: BizMaterialUsage, + *, + create_by_name: str = "", + batch: Optional[BizMaterialStockBatch] = None, + receiver_name: str = "", +) -> Dict[str, Any]: + """将 BizMaterialUsage 行转换为字典(用于我的领用记录)。""" + create_by = r.create_by or "" + receiver = (getattr(r, "receiver", None) or "").strip() + purpose = (getattr(r, "purpose", None) or "").strip() + create_time = r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "" + stock_after = float(r.stock_after) if r.stock_after is not None else 0 + change_qty = float(r.change_qty) if r.change_qty is not None else 0 + stock_before = ( + float(r.stock_before) + if r.stock_before is not None + else stock_after - change_qty + ) + unit = (getattr(r, "usage_unit", None) or "").strip() + if not unit and batch: + unit = (batch.unit or "").strip() + return { + "id": r.id, + "materialId": r.material_id, + "materialCode": r.material_code or "", + "materialName": r.material_name or "", + "batchId": r.batch_id or "", + "stockInNo": r.stock_in_no or "", + "catalogNo": r.catalog_no or "", + "batchNo": r.batch_no or "", + "manufacturer": r.manufacturer or "", + "unitPrice": float(r.unit_price) if r.unit_price is not None else None, + "amount": float(r.amount) if r.amount is not None else None, + "changeType": r.change_type, + "changeQty": change_qty, + "usageUnit": unit, + "stockBefore": stock_before, + "stockAfter": stock_after, + "receiver": receiver, + "receiverName": receiver_name or receiver, + "purpose": purpose, + "sourceType": r.source_type or "", + "requisitionStatus": (getattr(r, "requisition_status", None) or "open").strip() or "open", + "useTime": create_time, + "createBy": create_by, + "createByName": create_by_name or create_by, + "createTime": create_time, + "updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "", + } + + +def _my_usage_batch_map(db: Session, batch_ids: List[str]) -> Dict[str, BizMaterialStockBatch]: + ids = [x for x in batch_ids if x] + if not ids: + return {} + rows = ( + db.query(BizMaterialStockBatch) + .filter(BizMaterialStockBatch.id.in_(ids), BizMaterialStockBatch.del_flag == 0) + .all() + ) + return {r.id: r for r in rows} + + +def _my_usage_user_nick_name_map(db: Session, user_ids: List[str]) -> Dict[str, str]: + ids = list({x for x in user_ids if x}) + if not ids: + return {} + from app.models.models import SysUser + rows = db.query(SysUser.id, SysUser.nick_name).filter(SysUser.id.in_(ids)).all() + return {u.id: (u.nick_name or "").strip() for u in rows} + + +def get_my_usages( + db: Session, + current_user_id: str, + tenant_no: str, + *, + page: int = 1, + page_size: int = 10, + keyword: Optional[str] = None, + material_id: Optional[str] = None, + material_type: Optional[str] = None, + change_type: Optional[str] = None, + start_time: Optional[str] = None, + end_time: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], int]: + """ + 获取当前用户的物料领用记录(手动出库记录) + + 筛选条件: + - tenant_no = 当前用户租户 + - source_type = 'manual_out'(手动出库) + - receiver = 当前用户ID + - del_flag = 0 + + 按 create_time DESC 排序 + """ + q0 = db.query(BizMaterialUsage).filter( + BizMaterialUsage.tenant_no == tenant_no, + BizMaterialUsage.del_flag == 0, + BizMaterialUsage.source_type == "manual_out", + BizMaterialUsage.receiver == current_user_id, + ) + + if material_id: + q0 = q0.filter(BizMaterialUsage.material_id == material_id) + + kw = (keyword or "").strip() + if kw: + like = f"%{kw}%" + q0 = q0.filter( + or_( + BizMaterialUsage.material_code.like(like), + BizMaterialUsage.material_name.like(like), + BizMaterialUsage.stock_in_no.like(like), + BizMaterialUsage.batch_no.like(like), + BizMaterialUsage.catalog_no.like(like), + BizMaterialUsage.purpose.like(like), + ) + ) + + if start_time: + q0 = q0.filter(BizMaterialUsage.create_time >= start_time) + if end_time: + q0 = q0.filter(BizMaterialUsage.create_time <= end_time + " 23:59:59") + + total = q0.order_by(None).with_entities(func.count(BizMaterialUsage.id)).scalar() or 0 + page = max(1, page) + page_size = min(max(1, page_size), 100) + rows = ( + q0.order_by(BizMaterialUsage.create_time.desc()) + .offset((page - 1) * page_size) + .limit(page_size) + .all() + ) + + bmap = _my_usage_batch_map(db, [x.batch_id for x in rows if x.batch_id]) + name_map = _my_usage_user_nick_name_map(db, [x.create_by for x in rows if x.create_by]) + + return [ + _my_usage_row_to_dict( + x, + create_by_name=name_map.get(x.create_by or "", ""), + batch=bmap.get(x.batch_id or ""), + ) + for x in rows + ], int(total)