From 85faef213eda0dfe04d39dcbb5fd8792636c5fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A6=E9=BE=99=E8=A8=80?= Date: Mon, 27 Jul 2026 10:50:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E6=8C=89=E5=8F=91?= =?UTF-8?q?=E8=B4=A7=E7=8A=B6=E6=80=81=E8=AF=BB=E5=8F=96=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E4=BD=99=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module_business/dao/operation_dao.py | 45 +++++++++++----- .../module_business/test_shipment_service.py | 51 ++++++++++++++++++- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/shierfeng-fastapi-backend/module_business/dao/operation_dao.py b/shierfeng-fastapi-backend/module_business/dao/operation_dao.py index 85fea8a..3271eaa 100644 --- a/shierfeng-fastapi-backend/module_business/dao/operation_dao.py +++ b/shierfeng-fastapi-backend/module_business/dao/operation_dao.py @@ -1,11 +1,12 @@ from datetime import datetime, time, timedelta from typing import Any -from sqlalchemy import ColumnElement, Select, delete, false, or_, select, update +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.entity.do.master_do import BizPallet from module_business.entity.do.operation_do import ( BizFillingRecord, @@ -656,19 +657,37 @@ class OperationDao: if shipment is None: return None - item_result = await db.execute( - select(BizShipmentItem, BizPalletInventory.remaining_packages) - .outerjoin( - BizPalletInventory, - BizPalletInventory.pallet_id == BizShipmentItem.pallet_id, + 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, + ), + ) + .where(BizShipmentItem.shipment_id == shipment_id) + .order_by(BizShipmentItem.shipment_item_id) ) - .where(BizShipmentItem.shipment_id == shipment_id) - .order_by(BizShipmentItem.shipment_item_id) - ) - items = [] - for item, remaining_packages in item_result.all(): - item.remaining_packages = remaining_packages - items.append(item) + 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) + .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 diff --git a/shierfeng-fastapi-backend/tests/module_business/test_shipment_service.py b/shierfeng-fastapi-backend/tests/module_business/test_shipment_service.py index ba805e6..1f8cdb4 100644 --- a/shierfeng-fastapi-backend/tests/module_business/test_shipment_service.py +++ b/shierfeng-fastapi-backend/tests/module_business/test_shipment_service.py @@ -253,7 +253,7 @@ class ShipmentInfrastructureContractTest(unittest.TestCase): class ShipmentDetailDaoTest(unittest.IsolatedAsyncioTestCase): - async def test_detail_loads_each_current_remaining_quantity_with_one_left_join(self) -> None: + async def test_draft_detail_loads_only_current_shippable_remaining_quantity_with_one_left_join(self) -> None: db = MagicMock(spec=AsyncSession) shipment = make_shipment(pallet_ids=(5, 2, 8)) order_result = MagicMock() @@ -288,12 +288,59 @@ class ShipmentDetailDaoTest(unittest.IsolatedAsyncioTestCase): ) self.assertIn( 'LEFT OUTER JOIN biz_pallet_inventory ' - 'ON biz_pallet_inventory.pallet_id = biz_shipment_item.pallet_id', + 'ON biz_pallet_inventory.pallet_id = biz_shipment_item.pallet_id ' + 'AND biz_pallet_inventory.warehouse_id = 7 ' + "AND biz_pallet_inventory.flow_state = 'FILLED_IN_WAREHOUSE' " + 'AND biz_pallet_inventory.site_id IS NULL ' + 'AND biz_pallet_inventory.current_line_id IS NULL ' + 'AND biz_pallet_inventory.active_filling_id IS NOT NULL ' + 'AND biz_pallet_inventory.remaining_packages > 0', item_sql, ) self.assertIn('WHERE biz_shipment_item.shipment_id = 12', item_sql) self.assertIn('ORDER BY biz_shipment_item.shipment_item_id', item_sql) + async def test_confirmed_detail_preserves_snapshot_and_does_not_read_current_inventory(self) -> None: + db = MagicMock(spec=AsyncSession) + shipment = make_shipment(status='CONFIRMED', pallet_ids=(5, 2)) + shipment.items[0].active_filling_id = 105 + shipment.items[0].package_qty = 13 + shipment.items[1].active_filling_id = 102 + shipment.items[1].package_qty = 6 + order_result = MagicMock() + order_result.scalar_one_or_none.return_value = shipment + item_result = MagicMock() + item_result.all.return_value = [ + (shipment.items[0], 1), + (shipment.items[1], 2), + ] + item_result.scalars.return_value.all.return_value = shipment.items + db.execute.side_effect = [order_result, item_result] + + actual = await OperationDao.get_shipment_by_id( + db, + 12, + warehouse_ids=[7], + site_ids=[9], + ) + + self.assertIs(shipment, actual) + self.assertEqual(2, db.execute.await_count) + self.assertEqual([13, 6], [item.package_qty for item in shipment.items]) + self.assertEqual([None, None], [item.remaining_packages for item in shipment.items]) + item_statement = db.execute.await_args_list[1].args[0] + item_sql = ' '.join( + str( + item_statement.compile( + dialect=postgresql.dialect(), + compile_kwargs={'literal_binds': True}, + ) + ).split() + ) + self.assertNotIn('biz_pallet_inventory', item_sql) + self.assertIn('WHERE biz_shipment_item.shipment_id = 12', item_sql) + self.assertIn('ORDER BY biz_shipment_item.shipment_item_id', item_sql) + class ShipmentControllerBootstrapTest(unittest.TestCase): def test_shipment_controller_module_exists(self) -> None: