357 lines
12 KiB
Python
357 lines
12 KiB
Python
"""存储设施(树结构)+ 小格 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]
|