from datetime import datetime, time, timedelta from typing import Any from sqlalchemy import ColumnElement, Select, and_, delete, false, or_, select, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm.attributes import InstrumentedAttribute from common.vo import PageModel from module_business.domain.enums import DocumentStatus, PalletFlowState from module_business.domain.test_batch_scope import TestBatchScope, batch_scope_condition from module_business.entity.do.master_do import BizPallet from module_business.entity.do.operation_do import ( BizFillingRecord, BizInboundItem, BizInboundOrder, BizPalletInventory, BizReturnItem, BizReturnOrder, BizShipmentItem, BizShipmentOrder, BizUsageRecord, ) from module_business.entity.vo.operation_vo import ( FillingPageQueryModel, InboundPageQueryModel, ReturnPageQueryModel, ShipmentPageQueryModel, UsagePageQueryModel, ) from utils.page_util import PageUtil class OperationDao: """托盘入库、装填、发货、使用和退回单据数据库操作层。""" @staticmethod def _time_conditions( column: InstrumentedAttribute, begin_time: str | None, end_time: str | None, ) -> list[ColumnElement[bool]]: conditions = [] if begin_time: begin_at = datetime.combine( datetime.strptime(begin_time, '%Y-%m-%d').date(), time.min, ) conditions.append(column >= begin_at) if end_time: end_at = datetime.combine( datetime.strptime(end_time, '%Y-%m-%d').date() + timedelta(days=1), time.min, ) conditions.append(column < end_at) return conditions @staticmethod def _inbound_scope_conditions( warehouse_ids: list[int] | None, scope: TestBatchScope, ) -> list[ColumnElement[bool]]: if warehouse_ids is None: return [batch_scope_condition(BizInboundOrder.test_batch_id, scope)] scoped_warehouse_ids = sorted(set(warehouse_ids)) return [ batch_scope_condition(BizInboundOrder.test_batch_id, scope), BizInboundOrder.warehouse_id.in_(scoped_warehouse_ids) if scoped_warehouse_ids else false(), ] @staticmethod def _filling_scope_conditions( warehouse_ids: list[int] | None, scope: TestBatchScope, ) -> list[ColumnElement[bool]]: if warehouse_ids is None: return [batch_scope_condition(BizFillingRecord.test_batch_id, scope)] scoped_warehouse_ids = sorted(set(warehouse_ids)) return [ batch_scope_condition(BizFillingRecord.test_batch_id, scope), BizFillingRecord.warehouse_id.in_(scoped_warehouse_ids) if scoped_warehouse_ids else false(), ] @staticmethod def _usage_scope_conditions( site_ids: list[int] | None, scope: TestBatchScope, ) -> list[ColumnElement[bool]]: if site_ids is None: return [batch_scope_condition(BizUsageRecord.test_batch_id, scope)] scoped_site_ids = sorted(set(site_ids)) return [ batch_scope_condition(BizUsageRecord.test_batch_id, scope), BizUsageRecord.site_id.in_(scoped_site_ids) if scoped_site_ids else false(), ] @staticmethod def _shipment_scope_conditions( warehouse_ids: list[int] | None, site_ids: list[int] | None, scope: TestBatchScope, ) -> list[ColumnElement[bool]]: conditions = [batch_scope_condition(BizShipmentOrder.test_batch_id, scope)] if warehouse_ids is not None: scoped_warehouse_ids = sorted(set(warehouse_ids)) conditions.append( BizShipmentOrder.source_warehouse_id.in_(scoped_warehouse_ids) if scoped_warehouse_ids else false() ) if site_ids is not None: scoped_site_ids = sorted(set(site_ids)) conditions.append(BizShipmentOrder.target_site_id.in_(scoped_site_ids) if scoped_site_ids else false()) return conditions @staticmethod def _return_scope_conditions( site_ids: list[int] | None, warehouse_ids: list[int] | None, scope: TestBatchScope, ) -> list[ColumnElement[bool]]: if site_ids is None and warehouse_ids is None: return [batch_scope_condition(BizReturnOrder.test_batch_id, scope)] visibility_conditions: list[ColumnElement[bool]] = [] if site_ids is not None: scoped_site_ids = sorted(set(site_ids)) visibility_conditions.append( BizReturnOrder.source_site_id.in_(scoped_site_ids) if scoped_site_ids else false() ) if warehouse_ids is not None: scoped_warehouse_ids = sorted(set(warehouse_ids)) visibility_conditions.append( BizReturnOrder.target_warehouse_id.in_(scoped_warehouse_ids) if scoped_warehouse_ids else false() ) return [batch_scope_condition(BizReturnOrder.test_batch_id, scope), or_(*visibility_conditions)] @staticmethod async def _paginate( db: AsyncSession, statement: Select, query_object: Any, is_page: bool, ) -> PageModel | list[dict[str, Any]]: return await PageUtil.paginate( db, statement, query_object.page_num, query_object.page_size, is_page, exclude_fields={'test_batch_id'}, ) @staticmethod async def _get_for_update( db: AsyncSession, model: type[Any], id_column: InstrumentedAttribute, record_id: int, *extra_conditions: ColumnElement[bool], ) -> Any | None: result = await db.execute( select(model) .where(id_column == record_id, *extra_conditions) .with_for_update() .execution_options(populate_existing=True) ) return result.scalar_one_or_none() @staticmethod async def _get_by_key( db: AsyncSession, model: type[Any], key_column: InstrumentedAttribute, idempotency_key: str, *extra_conditions: ColumnElement[bool], ) -> Any | None: result = await db.execute(select(model).where(key_column == idempotency_key, *extra_conditions)) return result.scalar_one_or_none() @staticmethod async def _get_by_id( db: AsyncSession, model: type[Any], id_column: InstrumentedAttribute, record_id: int, *extra_conditions: ColumnElement[bool], ) -> Any | None: result = await db.execute(select(model).where(id_column == record_id, *extra_conditions)) return result.scalar_one_or_none() @classmethod async def _get_order_detail( cls, db: AsyncSession, order_model: type[Any], order_id_column: InstrumentedAttribute, order_id: int, item_model: type[Any], item_order_id_column: InstrumentedAttribute, item_id_column: InstrumentedAttribute, scope: TestBatchScope, *order_conditions: ColumnElement[bool], ) -> Any | None: order = await cls._get_by_id( db, order_model, order_id_column, order_id, *order_conditions, ) if order is None: return None item_result = await db.execute( select(item_model) .where(item_order_id_column == order_id, batch_scope_condition(item_model.test_batch_id, scope)) .order_by(item_id_column) ) order.items = list(item_result.scalars().all()) return order @staticmethod async def _add(db: AsyncSession, record: Any) -> Any: db.add(record) await db.flush() return record @staticmethod async def _update( db: AsyncSession, model: type[Any], id_column: InstrumentedAttribute, record: Any, ) -> Any: primary_key = id_column.key payload = { column.key: vars(record)[column.key] for column in model.__table__.columns if column.key not in {primary_key, 'create_by', 'create_time'} and column.key in vars(record) } if payload: await db.execute(update(model).where(id_column == getattr(record, primary_key)).values(**payload)) await db.flush() return record @staticmethod async def _delete( db: AsyncSession, model: type[Any], id_column: InstrumentedAttribute, record_id: int, ) -> None: await db.execute(delete(model).where(id_column == record_id)) await db.flush() @staticmethod async def _replace_items( db: AsyncSession, item_model: type[Any], order_id_column: InstrumentedAttribute, order_id: int, items: list[Any], ) -> list[Any]: await db.execute(delete(item_model).where(order_id_column == order_id)) for item in items: setattr(item, order_id_column.key, order_id) if items: db.add_all(items) await db.flush() return items @staticmethod async def _add_items(db: AsyncSession, items: list[Any]) -> list[Any]: if items: db.add_all(items) await db.flush() return items @classmethod async def get_inbound_for_update( cls, db: AsyncSession, inbound_id: int, scope: TestBatchScope, warehouse_ids: list[int] | None = None, ) -> BizInboundOrder | None: inbound = await cls._get_for_update( db, BizInboundOrder, BizInboundOrder.inbound_id, inbound_id, *cls._inbound_scope_conditions(warehouse_ids, scope), ) if inbound is None: return None # 父单锁定后用 locking read 获取最新明细,避免 MySQL RR 继续读取确认前的旧快照。 item_result = await db.execute( select(BizInboundItem) .where( BizInboundItem.inbound_id == inbound_id, batch_scope_condition(BizInboundItem.test_batch_id, scope), ) .order_by(BizInboundItem.pallet_id, BizInboundItem.inbound_item_id) .with_for_update() .execution_options(populate_existing=True) ) inbound.items = sorted( item_result.scalars().all(), key=lambda item: (item.pallet_id, item.inbound_item_id), ) return inbound @classmethod async def get_shipment_for_update( cls, db: AsyncSession, shipment_id: int, scope: TestBatchScope, warehouse_ids: list[int] | None = None, site_ids: list[int] | None = None, ) -> BizShipmentOrder | None: shipment = await cls._get_for_update( db, BizShipmentOrder, BizShipmentOrder.shipment_id, shipment_id, *cls._shipment_scope_conditions(warehouse_ids, site_ids, scope), ) if shipment is None: return None # 父单锁定后读取当前明细,避免 MySQL RR 沿用确认前的快照。 item_result = await db.execute( select(BizShipmentItem) .where( BizShipmentItem.shipment_id == shipment_id, batch_scope_condition(BizShipmentItem.test_batch_id, scope), ) .order_by(BizShipmentItem.pallet_id, BizShipmentItem.shipment_item_id) .with_for_update() .execution_options(populate_existing=True) ) shipment.items = sorted( item_result.scalars().all(), key=lambda item: (item.pallet_id, item.shipment_item_id), ) return shipment @classmethod async def get_return_for_update( cls, db: AsyncSession, return_id: int, scope: TestBatchScope, site_ids: list[int] | None = None, warehouse_ids: list[int] | None = None, ) -> BizReturnOrder | None: return_order = await cls._get_for_update( db, BizReturnOrder, BizReturnOrder.return_id, return_id, *cls._return_scope_conditions(site_ids, warehouse_ids, scope), ) if return_order is None: return None # 父单锁定后使用 locking read 获取当前明细,避免 MySQL RR 沿用旧快照。 item_result = await db.execute( select(BizReturnItem) .where( BizReturnItem.return_id == return_id, batch_scope_condition(BizReturnItem.test_batch_id, scope), ) .order_by(BizReturnItem.pallet_id, BizReturnItem.return_item_id) .with_for_update() .execution_options(populate_existing=True) ) return_order.items = sorted( item_result.scalars().all(), key=lambda item: (item.pallet_id, item.return_item_id), ) return return_order @classmethod async def get_active_filling_for_update( cls, db: AsyncSession, filling_id: int, scope: TestBatchScope, ) -> BizFillingRecord | None: """按已锁库存的 active_filling_id 锁定仍处于活动状态的装填周期。""" return await cls._get_for_update( db, BizFillingRecord, BizFillingRecord.filling_id, filling_id, batch_scope_condition(BizFillingRecord.test_batch_id, scope), BizFillingRecord.active_slot == 'ACTIVE', ) @classmethod async def get_inbound_by_confirm_idempotency_key( cls, db: AsyncSession, idempotency_key: str, scope: TestBatchScope, warehouse_ids: list[int] | None = None, ) -> BizInboundOrder | None: return await cls._get_by_key( db, BizInboundOrder, BizInboundOrder.confirm_idempotency_key, idempotency_key, *cls._inbound_scope_conditions(warehouse_ids, scope), ) @classmethod async def get_shipment_by_confirm_idempotency_key( cls, db: AsyncSession, idempotency_key: str, scope: TestBatchScope, warehouse_ids: list[int] | None = None, site_ids: list[int] | None = None, ) -> BizShipmentOrder | None: return await cls._get_by_key( db, BizShipmentOrder, BizShipmentOrder.confirm_idempotency_key, idempotency_key, *cls._shipment_scope_conditions(warehouse_ids, site_ids, scope), ) @classmethod async def get_filling_by_idempotency_key( cls, db: AsyncSession, idempotency_key: str, scope: TestBatchScope, warehouse_ids: list[int] | None = None, ) -> BizFillingRecord | None: return await cls._get_by_key( db, BizFillingRecord, BizFillingRecord.idempotency_key, idempotency_key, *cls._filling_scope_conditions(warehouse_ids, scope), ) @classmethod async def get_usage_by_idempotency_key( cls, db: AsyncSession, idempotency_key: str, scope: TestBatchScope, site_ids: list[int] | None = None, ) -> BizUsageRecord | None: return await cls._get_by_key( db, BizUsageRecord, BizUsageRecord.idempotency_key, idempotency_key, *cls._usage_scope_conditions(site_ids, scope), ) @classmethod async def get_return_by_request_idempotency_key( cls, db: AsyncSession, idempotency_key: str, source_site_id: int, target_warehouse_id: int, scope: TestBatchScope, ) -> BizReturnOrder | None: return await cls._get_by_key( db, BizReturnOrder, BizReturnOrder.request_idempotency_key, idempotency_key, BizReturnOrder.source_site_id == source_site_id, BizReturnOrder.target_warehouse_id == target_warehouse_id, batch_scope_condition(BizReturnOrder.test_batch_id, scope), ) @classmethod async def get_return_by_confirm_idempotency_key( cls, db: AsyncSession, idempotency_key: str, source_site_id: int, target_warehouse_id: int, scope: TestBatchScope, ) -> BizReturnOrder | None: return await cls._get_by_key( db, BizReturnOrder, BizReturnOrder.confirm_idempotency_key, idempotency_key, BizReturnOrder.source_site_id == source_site_id, BizReturnOrder.target_warehouse_id == target_warehouse_id, batch_scope_condition(BizReturnOrder.test_batch_id, scope), ) @classmethod async def get_inbound_list( cls, db: AsyncSession, query_object: InboundPageQueryModel, warehouse_ids: list[int] | None, scope: TestBatchScope, is_page: bool = True, ) -> PageModel | list[dict[str, Any]]: conditions = cls._inbound_scope_conditions(warehouse_ids, scope) if query_object.inbound_no: conditions.append(BizInboundOrder.inbound_no.like(f'%{query_object.inbound_no}%')) if query_object.warehouse_id: conditions.append(BizInboundOrder.warehouse_id == query_object.warehouse_id) if query_object.status: conditions.append(BizInboundOrder.status == query_object.status.value) conditions.extend( cls._time_conditions( BizInboundOrder.create_time, query_object.begin_time, query_object.end_time, ) ) statement = select(BizInboundOrder).where(*conditions).order_by(BizInboundOrder.inbound_id) return await cls._paginate(db, statement, query_object, is_page) @classmethod async def get_inbound_by_id( cls, db: AsyncSession, inbound_id: int, scope: TestBatchScope, warehouse_ids: list[int] | None = None, ) -> BizInboundOrder | None: return await cls._get_order_detail( db, BizInboundOrder, BizInboundOrder.inbound_id, inbound_id, BizInboundItem, BizInboundItem.inbound_id, BizInboundItem.inbound_item_id, scope, *cls._inbound_scope_conditions(warehouse_ids, scope), ) @classmethod async def add_inbound( cls, db: AsyncSession, inbound: BizInboundOrder, ) -> BizInboundOrder: return await cls._add(db, inbound) @classmethod async def update_inbound( cls, db: AsyncSession, inbound: BizInboundOrder, ) -> BizInboundOrder: return await cls._update( db, BizInboundOrder, BizInboundOrder.inbound_id, inbound, ) @classmethod async def delete_inbound(cls, db: AsyncSession, inbound_id: int) -> None: await db.execute(delete(BizInboundItem).where(BizInboundItem.inbound_id == inbound_id)) await cls._delete(db, BizInboundOrder, BizInboundOrder.inbound_id, inbound_id) @classmethod async def replace_inbound_items( cls, db: AsyncSession, inbound_id: int, items: list[BizInboundItem], ) -> list[BizInboundItem]: return await cls._replace_items( db, BizInboundItem, BizInboundItem.inbound_id, inbound_id, items, ) @classmethod async def get_filling_list( cls, db: AsyncSession, query_object: FillingPageQueryModel, warehouse_ids: list[int] | None, scope: TestBatchScope, is_page: bool = True, ) -> PageModel | list[dict[str, Any]]: conditions = cls._filling_scope_conditions(warehouse_ids, scope) if query_object.filling_no: conditions.append(BizFillingRecord.filling_no.like(f'%{query_object.filling_no}%')) if query_object.warehouse_id: conditions.append(BizFillingRecord.warehouse_id == query_object.warehouse_id) if query_object.strain_id: conditions.append(BizFillingRecord.strain_id == query_object.strain_id) if query_object.batch_no: conditions.append(BizFillingRecord.batch_no.like(f'%{query_object.batch_no}%')) if query_object.pallet_code: conditions.append(BizPallet.pallet_code.like(f'%{query_object.pallet_code}%')) conditions.extend( cls._time_conditions( BizFillingRecord.operated_at, query_object.begin_time, query_object.end_time, ) ) statement = select(BizFillingRecord) if query_object.pallet_code: statement = statement.join( BizPallet, BizPallet.pallet_id == BizFillingRecord.pallet_id, ) statement = statement.where(*conditions).order_by(BizFillingRecord.filling_id) return await cls._paginate(db, statement, query_object, is_page) @classmethod async def get_filling_by_id( cls, db: AsyncSession, filling_id: int, scope: TestBatchScope, warehouse_ids: list[int] | None = None, ) -> BizFillingRecord | None: return await cls._get_by_id( db, BizFillingRecord, BizFillingRecord.filling_id, filling_id, *cls._filling_scope_conditions(warehouse_ids, scope), ) @classmethod async def add_filling_record( cls, db: AsyncSession, filling: BizFillingRecord, ) -> BizFillingRecord: return await cls._add(db, filling) @classmethod async def update_filling_record( cls, db: AsyncSession, filling: BizFillingRecord, ) -> BizFillingRecord: return await cls._update( db, BizFillingRecord, BizFillingRecord.filling_id, filling, ) @classmethod async def get_shipment_list( cls, db: AsyncSession, query_object: ShipmentPageQueryModel, warehouse_ids: list[int] | None, site_ids: list[int] | None, scope: TestBatchScope, is_page: bool = True, ) -> PageModel | list[dict[str, Any]]: conditions = cls._shipment_scope_conditions(warehouse_ids, site_ids, scope) if query_object.shipment_no: conditions.append(BizShipmentOrder.shipment_no.like(f'%{query_object.shipment_no}%')) if query_object.source_warehouse_id: conditions.append(BizShipmentOrder.source_warehouse_id == query_object.source_warehouse_id) if query_object.target_site_id: conditions.append(BizShipmentOrder.target_site_id == query_object.target_site_id) if query_object.status: conditions.append(BizShipmentOrder.status == query_object.status.value) conditions.extend( cls._time_conditions( BizShipmentOrder.create_time, query_object.begin_time, query_object.end_time, ) ) statement = select(BizShipmentOrder).where(*conditions).order_by(BizShipmentOrder.shipment_id) return await cls._paginate(db, statement, query_object, is_page) @classmethod async def get_shipment_by_id( cls, db: AsyncSession, shipment_id: int, scope: TestBatchScope, warehouse_ids: list[int] | None = None, site_ids: list[int] | None = None, ) -> BizShipmentOrder | None: shipment = await cls._get_by_id( db, BizShipmentOrder, BizShipmentOrder.shipment_id, shipment_id, *cls._shipment_scope_conditions(warehouse_ids, site_ids, scope), ) if shipment is None: return None if shipment.status == DocumentStatus.DRAFT.value: item_result = await db.execute( select(BizShipmentItem, BizPalletInventory.remaining_packages) .outerjoin( BizPalletInventory, and_( BizPalletInventory.pallet_id == BizShipmentItem.pallet_id, BizPalletInventory.warehouse_id == shipment.source_warehouse_id, BizPalletInventory.flow_state == PalletFlowState.FILLED_IN_WAREHOUSE.value, BizPalletInventory.site_id.is_(None), BizPalletInventory.current_line_id.is_(None), BizPalletInventory.active_filling_id.is_not(None), BizPalletInventory.remaining_packages > 0, batch_scope_condition(BizPalletInventory.test_batch_id, scope), ), ) .where( BizShipmentItem.shipment_id == shipment_id, batch_scope_condition(BizShipmentItem.test_batch_id, scope), ) .order_by(BizShipmentItem.shipment_item_id) ) items = [] for item, remaining_packages in item_result.all(): item.remaining_packages = remaining_packages items.append(item) else: item_result = await db.execute( select(BizShipmentItem) .where( BizShipmentItem.shipment_id == shipment_id, batch_scope_condition(BizShipmentItem.test_batch_id, scope), ) .order_by(BizShipmentItem.shipment_item_id) ) items = list(item_result.scalars().all()) for item in items: item.remaining_packages = None shipment.items = items return shipment @classmethod async def add_shipment( cls, db: AsyncSession, shipment: BizShipmentOrder, ) -> BizShipmentOrder: return await cls._add(db, shipment) @classmethod async def update_shipment( cls, db: AsyncSession, shipment: BizShipmentOrder, ) -> BizShipmentOrder: return await cls._update( db, BizShipmentOrder, BizShipmentOrder.shipment_id, shipment, ) @classmethod async def delete_shipment(cls, db: AsyncSession, shipment_id: int) -> None: await db.execute(delete(BizShipmentItem).where(BizShipmentItem.shipment_id == shipment_id)) await cls._delete( db, BizShipmentOrder, BizShipmentOrder.shipment_id, shipment_id, ) @classmethod async def replace_shipment_items( cls, db: AsyncSession, shipment_id: int, items: list[BizShipmentItem], ) -> list[BizShipmentItem]: return await cls._replace_items( db, BizShipmentItem, BizShipmentItem.shipment_id, shipment_id, items, ) @classmethod async def get_usage_list( cls, db: AsyncSession, query_object: UsagePageQueryModel, site_ids: list[int] | None, scope: TestBatchScope, is_page: bool = True, ) -> PageModel | list[dict[str, Any]]: conditions = cls._usage_scope_conditions(site_ids, scope) if query_object.usage_no: conditions.append(BizUsageRecord.usage_no.like(f'%{query_object.usage_no}%')) if query_object.site_id: conditions.append(BizUsageRecord.site_id == query_object.site_id) if query_object.line_id: conditions.append(BizUsageRecord.line_id == query_object.line_id) if query_object.pallet_code: conditions.append(BizPallet.pallet_code.like(f'%{query_object.pallet_code}%')) conditions.extend( cls._time_conditions( BizUsageRecord.occurred_at, query_object.begin_time, query_object.end_time, ) ) statement = select(BizUsageRecord) if query_object.pallet_code: statement = statement.join( BizPallet, BizPallet.pallet_id == BizUsageRecord.pallet_id, ) statement = statement.where(*conditions).order_by(BizUsageRecord.usage_id) return await cls._paginate(db, statement, query_object, is_page) @classmethod async def get_usage_by_id( cls, db: AsyncSession, usage_id: int, scope: TestBatchScope, site_ids: list[int] | None = None, ) -> BizUsageRecord | None: return await cls._get_by_id( db, BizUsageRecord, BizUsageRecord.usage_id, usage_id, *cls._usage_scope_conditions(site_ids, scope), ) @classmethod async def add_usage_record( cls, db: AsyncSession, usage: BizUsageRecord, ) -> BizUsageRecord: return await cls._add(db, usage) @classmethod async def get_return_list( cls, db: AsyncSession, query_object: ReturnPageQueryModel, site_ids: list[int] | None, warehouse_ids: list[int] | None, scope: TestBatchScope, is_page: bool = True, ) -> PageModel | list[dict[str, Any]]: conditions = cls._return_scope_conditions(site_ids, warehouse_ids, scope) if query_object.return_no: conditions.append(BizReturnOrder.return_no.like(f'%{query_object.return_no}%')) if query_object.source_site_id: conditions.append(BizReturnOrder.source_site_id == query_object.source_site_id) if query_object.target_warehouse_id: conditions.append(BizReturnOrder.target_warehouse_id == query_object.target_warehouse_id) if query_object.status: conditions.append(BizReturnOrder.status == query_object.status.value) conditions.extend( cls._time_conditions( BizReturnOrder.requested_at, query_object.begin_time, query_object.end_time, ) ) statement = select(BizReturnOrder).where(*conditions).order_by(BizReturnOrder.return_id) return await cls._paginate(db, statement, query_object, is_page) @classmethod async def get_return_by_id( cls, db: AsyncSession, return_id: int, scope: TestBatchScope, site_ids: list[int] | None = None, warehouse_ids: list[int] | None = None, ) -> BizReturnOrder | None: return await cls._get_order_detail( db, BizReturnOrder, BizReturnOrder.return_id, return_id, BizReturnItem, BizReturnItem.return_id, BizReturnItem.return_item_id, scope, *cls._return_scope_conditions(site_ids, warehouse_ids, scope), ) @classmethod async def add_return_order( cls, db: AsyncSession, return_order: BizReturnOrder, ) -> BizReturnOrder: return await cls._add(db, return_order) @classmethod async def update_return_order( cls, db: AsyncSession, return_order: BizReturnOrder, ) -> BizReturnOrder: return await cls._update( db, BizReturnOrder, BizReturnOrder.return_id, return_order, ) @classmethod async def add_return_items( cls, db: AsyncSession, items: list[BizReturnItem], ) -> list[BizReturnItem]: return await cls._add_items(db, items)