Files
shanxia2.0/backend/app/crud/storage_facility.py
T
2026-05-17 08:36:37 +08:00

357 lines
12 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(biz_storage_facility / biz_storage_facility_slot)"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Dict, List, Optional
from sqlalchemy import and_, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.models import BizStorageFacility, BizStorageFacilitySlot
# ---------------------------------------------------------------------------
# Facility node helpers
# ---------------------------------------------------------------------------
def _row_to_dict(f: BizStorageFacility) -> Dict[str, Any]:
return {
"id": f.id,
"parentId": f.parent_id,
"nodeName": f.node_name,
"nodeType": f.node_type or "",
"isLeaf": 1 if f.is_leaf else 0,
"tenantNo": f.tenant_no,
"orderNum": f.order_num if f.order_num is not None else 0,
"remark": f.remark or "",
"createTime": f.create_time.strftime("%Y-%m-%d %H:%M:%S") if f.create_time else "",
"updateTime": f.update_time.strftime("%Y-%m-%d %H:%M:%S") if f.update_time else "",
}
def list_all_facilities(
db: Session,
*,
tenant_no: str,
keyword: Optional[str] = None,
) -> List[Dict[str, Any]]:
"""Return FLAT list of all non-deleted facility nodes (no pagination, no tree building)."""
q = db.query(BizStorageFacility).filter(BizStorageFacility.del_flag == 0)
if tenant_no:
q = q.filter(BizStorageFacility.tenant_no == tenant_no)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q = q.filter(BizStorageFacility.node_name.like(like))
rows = q.order_by(BizStorageFacility.order_num.asc(), BizStorageFacility.create_time.asc()).all()
return [_row_to_dict(x) for x in rows]
def get_facility(db: Session, node_id: str, tenant_no: Optional[str] = None) -> Optional[BizStorageFacility]:
q = db.query(BizStorageFacility).filter(
and_(BizStorageFacility.id == node_id, BizStorageFacility.del_flag == 0)
)
if tenant_no:
q = q.filter(BizStorageFacility.tenant_no == tenant_no)
return q.first()
def get_children(db: Session, parent_id: str, tenant_no: str) -> List[BizStorageFacility]:
"""Direct children of a node."""
return (
db.query(BizStorageFacility)
.filter(
and_(
BizStorageFacility.parent_id == parent_id,
BizStorageFacility.del_flag == 0,
BizStorageFacility.tenant_no == tenant_no,
)
)
.order_by(BizStorageFacility.order_num.asc(), BizStorageFacility.create_time.asc())
.all()
)
def collect_descendant_ids(db: Session, node_id: str, tenant_no: str) -> List[str]:
"""Iteratively collect ALL descendant node IDs (stack-based, no recursion)."""
ids: List[str] = []
stack: List[str] = [node_id]
while stack:
current = stack.pop()
children = get_children(db, current, tenant_no)
for child in children:
ids.append(child.id)
stack.append(child.id)
return ids
def create_facility(
db: Session,
*,
parent_id: str = "0",
node_name: str,
node_type: Optional[str] = None,
is_leaf: int = 0,
tenant_no: str = "zkxw",
order_num: Optional[int] = None,
remark: Optional[str] = None,
user_id: Optional[str] = None,
) -> BizStorageFacility:
name = (node_name or "").strip()
if not name:
raise ValueError("节点名称不能为空")
obj = BizStorageFacility(
id=generate_uuid(),
parent_id=parent_id or "0",
node_name=name,
node_type=(node_type or "").strip() or None,
is_leaf=is_leaf,
tenant_no=tenant_no,
order_num=order_num if order_num is not None else 0,
remark=(remark or "").strip() or None,
create_by=user_id,
create_dept_id="",
create_time=datetime.now(),
update_by=user_id,
update_time=datetime.now(),
del_flag=0,
)
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def update_facility(
db: Session,
node_id: str,
*,
node_name: Optional[str] = None,
node_type: Optional[str] = None,
is_leaf: Optional[int] = None,
order_num: Optional[int] = None,
remark: Optional[str] = None,
user_id: Optional[str] = None,
tenant_no: Optional[str] = None,
) -> BizStorageFacility:
obj = get_facility(db, node_id, tenant_no=tenant_no)
if not obj:
raise ValueError("存储设施节点不存在")
if node_name is not None:
name = (node_name or "").strip()
if not name:
raise ValueError("节点名称不能为空")
obj.node_name = name
if node_type is not None:
obj.node_type = (node_type or "").strip() or None
if is_leaf is not None:
obj.is_leaf = is_leaf
if order_num is not None:
obj.order_num = order_num
if remark is not None:
obj.remark = (remark or "").strip() or None
obj.update_by = user_id
obj.update_time = datetime.now()
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def soft_delete_facility(db: Session, node_id: str, tenant_no: Optional[str] = None) -> None:
"""Cascade soft-delete: node + all descendants + their slots."""
node = get_facility(db, node_id, tenant_no=tenant_no)
if not node:
raise ValueError("存储设施节点不存在")
# Collect all descendant IDs
all_ids = [node_id] + collect_descendant_ids(db, node_id, node.tenant_no)
now = datetime.now()
# Soft-delete all slots belonging to these nodes
db.query(BizStorageFacilitySlot).filter(
BizStorageFacilitySlot.facility_node_id.in_(all_ids),
BizStorageFacilitySlot.del_flag == 0,
).update({"del_flag": 1, "update_time": now}, synchronize_session=False)
# Soft-delete all facility nodes
db.query(BizStorageFacility).filter(
BizStorageFacility.id.in_(all_ids),
BizStorageFacility.del_flag == 0,
).update({"del_flag": 1, "update_time": now}, synchronize_session=False)
db.commit()
# ---------------------------------------------------------------------------
# Slot helpers
# ---------------------------------------------------------------------------
def _slot_to_dict(s: BizStorageFacilitySlot) -> Dict[str, Any]:
return {
"id": s.id,
"facilityNodeId": s.facility_node_id,
"slotCode": s.slot_code or "",
"slotStatus": s.slot_status or "",
"tenantNo": s.tenant_no,
"orderNum": s.order_num if s.order_num is not None else 0,
"remark": s.remark or "",
"createTime": s.create_time.strftime("%Y-%m-%d %H:%M:%S") if s.create_time else "",
"updateTime": s.update_time.strftime("%Y-%m-%d %H:%M:%S") if s.update_time else "",
}
def list_slots(
db: Session,
*,
facility_node_id: str,
tenant_no: Optional[str] = None,
) -> List[Dict[str, Any]]:
q = db.query(BizStorageFacilitySlot).filter(
and_(
BizStorageFacilitySlot.facility_node_id == facility_node_id,
BizStorageFacilitySlot.del_flag == 0,
)
)
if tenant_no:
q = q.filter(BizStorageFacilitySlot.tenant_no == tenant_no)
rows = q.order_by(BizStorageFacilitySlot.order_num.asc(), BizStorageFacilitySlot.create_time.asc()).all()
return [_slot_to_dict(x) for x in rows]
def get_slot(db: Session, slot_id: str, tenant_no: Optional[str] = None) -> Optional[BizStorageFacilitySlot]:
q = db.query(BizStorageFacilitySlot).filter(
and_(BizStorageFacilitySlot.id == slot_id, BizStorageFacilitySlot.del_flag == 0)
)
if tenant_no:
q = q.filter(BizStorageFacilitySlot.tenant_no == tenant_no)
return q.first()
def create_slot(
db: Session,
*,
facility_node_id: str,
slot_code: Optional[str] = None,
slot_status: Optional[str] = None,
tenant_no: str = "zkxw",
order_num: Optional[int] = None,
remark: Optional[str] = None,
user_id: Optional[str] = None,
) -> BizStorageFacilitySlot:
fid = (facility_node_id or "").strip()
if not fid:
raise ValueError("所属设施节点不能为空")
obj = BizStorageFacilitySlot(
id=generate_uuid(),
facility_node_id=fid,
slot_code=(slot_code or "").strip() or None,
slot_status=(slot_status or "").strip() or None,
tenant_no=tenant_no,
order_num=order_num if order_num is not None else 0,
remark=(remark or "").strip() or None,
create_by=user_id,
create_dept_id="",
create_time=datetime.now(),
update_by=user_id,
update_time=datetime.now(),
del_flag=0,
)
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def update_slot(
db: Session,
slot_id: str,
*,
slot_code: Optional[str] = None,
slot_status: Optional[str] = None,
order_num: Optional[int] = None,
remark: Optional[str] = None,
user_id: Optional[str] = None,
tenant_no: Optional[str] = None,
) -> BizStorageFacilitySlot:
obj = get_slot(db, slot_id, tenant_no=tenant_no)
if not obj:
raise ValueError("存储设施小格不存在")
if slot_code is not None:
obj.slot_code = (slot_code or "").strip() or None
if slot_status is not None:
obj.slot_status = (slot_status or "").strip() or None
if order_num is not None:
obj.order_num = order_num
if remark is not None:
obj.remark = (remark or "").strip() or None
obj.update_by = user_id
obj.update_time = datetime.now()
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def soft_delete_slot(db: Session, slot_id: str, tenant_no: Optional[str] = None) -> None:
obj = get_slot(db, slot_id, tenant_no=tenant_no)
if not obj:
raise ValueError("存储设施小格不存在")
obj.del_flag = 1
obj.update_time = datetime.now()
db.add(obj)
db.commit()
def batch_create_slots(
db: Session,
*,
facility_node_id: str,
rows: int,
cols: int,
code_prefix: str = "",
tenant_no: str = "zkxw",
user_id: Optional[str] = None,
) -> List[Dict[str, Any]]:
fid = (facility_node_id or "").strip()
if not fid:
raise ValueError("所属设施节点不能为空")
if rows < 1 or cols < 1:
raise ValueError("行数和列数必须大于 0")
if rows > 50 or cols > 50:
raise ValueError("单次最多生成 50×50 = 2500 个格子")
node = db.query(BizStorageFacility).filter(
and_(BizStorageFacility.id == fid, BizStorageFacility.del_flag == 0)
).first()
if not node:
raise ValueError("所属设施节点不存在")
if not node.is_leaf:
raise ValueError("只能为叶子节点创建格子")
prefix = (code_prefix or "").strip()
now = datetime.now()
slots: List[BizStorageFacilitySlot] = []
for r in range(1, rows + 1):
for c in range(1, cols + 1):
code = f"{prefix}{r}-{c}" if prefix else f"{r}-{c}"
order = (r - 1) * cols + c
obj = BizStorageFacilitySlot(
id=generate_uuid(),
facility_node_id=fid,
slot_code=code,
slot_status="空",
tenant_no=tenant_no,
order_num=order,
remark=None,
create_by=user_id,
create_dept_id="",
create_time=now,
update_by=user_id,
update_time=now,
del_flag=0,
)
db.add(obj)
slots.append(obj)
db.commit()
for s in slots:
db.refresh(s)
return [_slot_to_dict(s) for s in slots]