303 lines
9.3 KiB
Python
303 lines
9.3 KiB
Python
"""库位占用记录 CRUD(biz_storage_slot_occupancy)"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from sqlalchemy import and_, func
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.utils import generate_uuid
|
||
from app.models.models import BizStorageFacilitySlot, BizStorageSlotOccupancy
|
||
|
||
|
||
def _row_to_dict(c: BizStorageSlotOccupancy) -> Dict[str, Any]:
|
||
return {
|
||
"id": c.id,
|
||
"slotId": c.slot_id,
|
||
"itemType": c.item_type or "",
|
||
"itemId": c.item_id or "",
|
||
"itemCode": c.item_code or "",
|
||
"itemName": c.item_name or "",
|
||
"itemSource": c.item_source or "",
|
||
"storedAt": c.stored_at.strftime("%Y-%m-%d %H:%M:%S") if c.stored_at else "",
|
||
"removedAt": c.removed_at.strftime("%Y-%m-%d %H:%M:%S") if c.removed_at else "",
|
||
"occupancyStatus": c.occupancy_status or "",
|
||
"tenantNo": c.tenant_no,
|
||
"remark": c.remark or "",
|
||
"createBy": c.create_by or "",
|
||
"createTime": c.create_time.strftime("%Y-%m-%d %H:%M:%S") if c.create_time else "",
|
||
}
|
||
|
||
|
||
def _refresh_slot_cache(db: Session, slot_id: str) -> None:
|
||
"""刷新 slot 的 occupied_count / last_item_type / last_stored_at / slot_status"""
|
||
slot = db.query(BizStorageFacilitySlot).filter(
|
||
BizStorageFacilitySlot.id == slot_id,
|
||
BizStorageFacilitySlot.del_flag == 0,
|
||
).first()
|
||
if not slot:
|
||
return
|
||
|
||
count = db.query(func.count(BizStorageSlotOccupancy.id)).filter(
|
||
BizStorageSlotOccupancy.slot_id == slot_id,
|
||
BizStorageSlotOccupancy.occupancy_status == "stored",
|
||
BizStorageSlotOccupancy.del_flag == 0,
|
||
).scalar() or 0
|
||
|
||
slot.occupied_count = count
|
||
if count > 0:
|
||
slot.slot_status = "占用"
|
||
latest = db.query(BizStorageSlotOccupancy).filter(
|
||
BizStorageSlotOccupancy.slot_id == slot_id,
|
||
BizStorageSlotOccupancy.occupancy_status == "stored",
|
||
BizStorageSlotOccupancy.del_flag == 0,
|
||
).order_by(BizStorageSlotOccupancy.stored_at.desc()).first()
|
||
if latest:
|
||
slot.last_item_type = latest.item_type
|
||
slot.last_stored_at = latest.stored_at
|
||
else:
|
||
slot.slot_status = "空"
|
||
slot.last_item_type = None
|
||
slot.last_stored_at = None
|
||
slot.update_time = datetime.now()
|
||
db.add(slot)
|
||
db.flush()
|
||
|
||
|
||
def create_occupancy(
|
||
db: Session,
|
||
*,
|
||
slot_id: str,
|
||
item_type: str,
|
||
item_id: str,
|
||
item_code: str,
|
||
item_name: str,
|
||
item_source: str = "manual",
|
||
tenant_no: str = "",
|
||
user_id: Optional[str] = None,
|
||
remark: Optional[str] = None,
|
||
) -> BizStorageSlotOccupancy:
|
||
"""创建入库记录 + 刷新 slot 缓存"""
|
||
now = datetime.now()
|
||
obj = BizStorageSlotOccupancy(
|
||
id=generate_uuid(),
|
||
slot_id=slot_id,
|
||
item_type=item_type,
|
||
item_id=item_id,
|
||
item_code=(item_code or "").strip() or None,
|
||
item_name=(item_name or "").strip() or None,
|
||
item_source=(item_source or "manual").strip(),
|
||
stored_at=now,
|
||
removed_at=None,
|
||
occupancy_status="stored",
|
||
tenant_no=tenant_no,
|
||
order_num=0,
|
||
remark=(remark or "").strip() or None,
|
||
create_by=user_id,
|
||
create_dept_id="",
|
||
create_time=now,
|
||
update_by=user_id,
|
||
update_time=now,
|
||
del_flag=0,
|
||
)
|
||
db.add(obj)
|
||
db.flush()
|
||
_refresh_slot_cache(db, slot_id)
|
||
db.commit()
|
||
db.refresh(obj)
|
||
return obj
|
||
|
||
|
||
def batch_create_occupancies(
|
||
db: Session,
|
||
*,
|
||
items: List[Dict[str, Any]],
|
||
tenant_no: str = "",
|
||
user_id: Optional[str] = None,
|
||
) -> List[BizStorageSlotOccupancy]:
|
||
"""批量入库"""
|
||
now = datetime.now()
|
||
created: List[BizStorageSlotOccupancy] = []
|
||
slot_ids_seen: set = set()
|
||
for it in items:
|
||
obj = BizStorageSlotOccupancy(
|
||
id=generate_uuid(),
|
||
slot_id=it["slot_id"],
|
||
item_type=it.get("item_type", "cell_product"),
|
||
item_id=it["item_id"],
|
||
item_code=(it.get("item_code") or "").strip() or None,
|
||
item_name=(it.get("item_name") or "").strip() or None,
|
||
item_source=(it.get("item_source") or "production").strip(),
|
||
stored_at=now,
|
||
removed_at=None,
|
||
occupancy_status="stored",
|
||
tenant_no=tenant_no,
|
||
order_num=0,
|
||
remark=(it.get("remark") or "").strip() or None,
|
||
create_by=user_id,
|
||
create_dept_id="",
|
||
create_time=now,
|
||
update_by=user_id,
|
||
update_time=now,
|
||
del_flag=0,
|
||
)
|
||
db.add(obj)
|
||
created.append(obj)
|
||
slot_ids_seen.add(it["slot_id"])
|
||
db.flush()
|
||
for sid in slot_ids_seen:
|
||
_refresh_slot_cache(db, sid)
|
||
db.commit()
|
||
for obj in created:
|
||
db.refresh(obj)
|
||
return created
|
||
|
||
|
||
def remove_occupancy(
|
||
db: Session,
|
||
occupancy_id: str,
|
||
*,
|
||
user_id: Optional[str] = None,
|
||
) -> BizStorageSlotOccupancy:
|
||
"""手动出库"""
|
||
obj = db.query(BizStorageSlotOccupancy).filter(
|
||
BizStorageSlotOccupancy.id == occupancy_id,
|
||
BizStorageSlotOccupancy.del_flag == 0,
|
||
).first()
|
||
if not obj:
|
||
raise ValueError("占用记录不存在")
|
||
if obj.occupancy_status == "removed":
|
||
raise ValueError("该物品已出库")
|
||
now = datetime.now()
|
||
obj.occupancy_status = "removed"
|
||
obj.removed_at = now
|
||
obj.update_by = user_id
|
||
obj.update_time = now
|
||
db.add(obj)
|
||
db.flush()
|
||
_refresh_slot_cache(db, obj.slot_id)
|
||
db.commit()
|
||
db.refresh(obj)
|
||
return obj
|
||
|
||
|
||
def remove_by_item(
|
||
db: Session,
|
||
item_type: str,
|
||
item_id: str,
|
||
*,
|
||
user_id: Optional[str] = None,
|
||
) -> None:
|
||
"""按物品类型+ID出库(细胞产品状态变更时调用)"""
|
||
obj = db.query(BizStorageSlotOccupancy).filter(
|
||
BizStorageSlotOccupancy.item_type == item_type,
|
||
BizStorageSlotOccupancy.item_id == item_id,
|
||
BizStorageSlotOccupancy.occupancy_status == "stored",
|
||
BizStorageSlotOccupancy.del_flag == 0,
|
||
).first()
|
||
if not obj:
|
||
return
|
||
now = datetime.now()
|
||
obj.occupancy_status = "removed"
|
||
obj.removed_at = now
|
||
obj.update_by = user_id
|
||
obj.update_time = now
|
||
db.add(obj)
|
||
db.flush()
|
||
_refresh_slot_cache(db, obj.slot_id)
|
||
db.commit()
|
||
|
||
|
||
def list_occupancies(
|
||
db: Session,
|
||
*,
|
||
slot_id: str,
|
||
occupancy_status: Optional[str] = "stored",
|
||
item_type: Optional[str] = None,
|
||
tenant_no: str = "",
|
||
page: int = 1,
|
||
page_size: int = 20,
|
||
) -> Dict[str, Any]:
|
||
"""分页查询占用记录"""
|
||
q = db.query(BizStorageSlotOccupancy).filter(
|
||
BizStorageSlotOccupancy.slot_id == slot_id,
|
||
BizStorageSlotOccupancy.del_flag == 0,
|
||
)
|
||
if tenant_no:
|
||
q = q.filter(BizStorageSlotOccupancy.tenant_no == tenant_no)
|
||
if occupancy_status:
|
||
q = q.filter(BizStorageSlotOccupancy.occupancy_status == occupancy_status)
|
||
if item_type:
|
||
q = q.filter(BizStorageSlotOccupancy.item_type == item_type)
|
||
|
||
total = q.order_by(None).with_entities(func.count(BizStorageSlotOccupancy.id)).scalar() or 0
|
||
page = max(1, page)
|
||
page_size = min(max(1, page_size), 100)
|
||
rows = (
|
||
q.order_by(BizStorageSlotOccupancy.stored_at.desc())
|
||
.offset((page - 1) * page_size)
|
||
.limit(page_size)
|
||
.all()
|
||
)
|
||
return {"items": [_row_to_dict(r) for r in rows], "total": int(total)}
|
||
|
||
|
||
def list_occupancy_history(
|
||
db: Session,
|
||
*,
|
||
slot_id: Optional[str] = None,
|
||
facility_node_id: Optional[str] = None,
|
||
item_type: Optional[str] = None,
|
||
start_date: Optional[str] = None,
|
||
end_date: Optional[str] = None,
|
||
tenant_no: str = "",
|
||
page: int = 1,
|
||
page_size: int = 20,
|
||
) -> Dict[str, Any]:
|
||
"""出入库历史"""
|
||
q = db.query(BizStorageSlotOccupancy).filter(
|
||
BizStorageSlotOccupancy.del_flag == 0,
|
||
)
|
||
if tenant_no:
|
||
q = q.filter(BizStorageSlotOccupancy.tenant_no == tenant_no)
|
||
if slot_id:
|
||
q = q.filter(BizStorageSlotOccupancy.slot_id == slot_id)
|
||
if item_type:
|
||
q = q.filter(BizStorageSlotOccupancy.item_type == item_type)
|
||
if start_date:
|
||
try:
|
||
sd = datetime.strptime(start_date, "%Y-%m-%d")
|
||
q = q.filter(BizStorageSlotOccupancy.stored_at >= sd)
|
||
except ValueError:
|
||
pass
|
||
if end_date:
|
||
try:
|
||
ed = datetime.strptime(end_date, "%Y-%m-%d")
|
||
ed = ed.replace(hour=23, minute=59, second=59)
|
||
q = q.filter(BizStorageSlotOccupancy.stored_at <= ed)
|
||
except ValueError:
|
||
pass
|
||
if facility_node_id:
|
||
slot_ids = [
|
||
r[0] for r in db.query(BizStorageFacilitySlot.id).filter(
|
||
BizStorageFacilitySlot.facility_node_id == facility_node_id,
|
||
BizStorageFacilitySlot.del_flag == 0,
|
||
).all()
|
||
]
|
||
if slot_ids:
|
||
q = q.filter(BizStorageSlotOccupancy.slot_id.in_(slot_ids))
|
||
else:
|
||
return {"items": [], "total": 0}
|
||
|
||
total = q.order_by(None).with_entities(func.count(BizStorageSlotOccupancy.id)).scalar() or 0
|
||
page = max(1, page)
|
||
page_size = min(max(1, page_size), 100)
|
||
rows = (
|
||
q.order_by(BizStorageSlotOccupancy.create_time.desc())
|
||
.offset((page - 1) * page_size)
|
||
.limit(page_size)
|
||
.all()
|
||
)
|
||
return {"items": [_row_to_dict(r) for r in rows], "total": int(total)}
|