from __future__ import annotations import unittest from decimal import Decimal from sqlalchemy import BigInteger, create_engine, select from sqlalchemy.ext.compiler import compiles from sqlalchemy.orm import Session, sessionmaker @compiles(BigInteger, "sqlite") def _compile_bigint_for_sqlite(type_, compiler, **kw) -> str: _ = type_, compiler, kw return "INTEGER" import app.models.master_data # noqa: E402,F401 import app.models.operations # noqa: E402,F401 from app.api.routes.inventory import create_customer_supplied_inbound, create_warehouse_inbound # noqa: E402 from app.models.base import Base # noqa: E402 from app.models.master_data import Item, StockBalance, Warehouse # noqa: E402 from app.models.operations import StockLot # noqa: E402 from app.schemas.operations import CustomerSuppliedInboundCreate, WarehouseInboundCreate # noqa: E402 class User: id = 1 class Context: user = User() class CustomerSuppliedInboundLotNoTest(unittest.TestCase): def setUp(self) -> None: engine = create_engine("sqlite+pysqlite:///:memory:", future=True) Base.metadata.create_all(engine) self.SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True) self.db: Session = self.SessionLocal() self.material = Item( id=1, item_code="110501000185", item_name="左右后纵梁后段毛坯", item_type="RAW_MATERIAL", unit_weight_kg=Decimal("1"), status="ACTIVE", ) self.warehouse = Warehouse( id=1, warehouse_code="WH-RAW", warehouse_name="原材料库", warehouse_type="RAW", status="ACTIVE", ) self.db.add_all([self.material, self.warehouse]) self.db.commit() def tearDown(self) -> None: self.db.close() def _seed_existing_lot(self, lot_no: str) -> None: self.db.add( StockLot( lot_no=lot_no, lot_role="CUSTOMER_SUPPLIED_RAW", item_id=self.material.id, warehouse_id=self.warehouse.id, source_doc_type="CUSTOMER_SUPPLIED_IN", source_doc_id=1, inbound_qty=Decimal("0"), inbound_weight_kg=Decimal("100"), remaining_qty=Decimal("0"), remaining_weight_kg=Decimal("100"), locked_qty=Decimal("0"), locked_weight_kg=Decimal("0"), unit_cost=Decimal("0"), quality_status="PASS", status="AVAILABLE", ) ) self.db.commit() def test_generic_customer_supplied_inbound_ignores_manual_lot_no_and_uses_global_raw_sequence(self) -> None: self._seed_existing_lot("JH_左右后纵梁后段毛坯00185_0001") first_row = create_warehouse_inbound( WarehouseInboundCreate( biz_type="CUSTOMER_SUPPLIED", item_id=self.material.id, warehouse_id=self.warehouse.id, inbound_weight_kg=50, unit_cost=0, lot_no="用户手填批次号", source_material_sub_batch_no="用户手填来源批次", provider_name="客户A", waybill_no="YD-KL-001", freight_amount=0, ), context=Context(), db=self.db, ) second_row = create_warehouse_inbound( WarehouseInboundCreate( biz_type="CUSTOMER_SUPPLIED", item_id=self.material.id, warehouse_id=self.warehouse.id, inbound_weight_kg=25, unit_cost=0, lot_no="第二个用户手填批次号", source_material_sub_batch_no="第二个用户手填来源批次", provider_name="客户A", waybill_no="YD-KL-002", freight_amount=0, ), context=Context(), db=self.db, ) lot = self.db.scalar(select(StockLot).where(StockLot.id == first_row.lot_id)) second_lot = self.db.scalar(select(StockLot).where(StockLot.id == second_row.lot_id)) balance = self.db.scalar( select(StockBalance).where( StockBalance.item_id == self.material.id, StockBalance.warehouse_id == self.warehouse.id, ) ) self.assertEqual(lot.lot_no, "YL0001") self.assertEqual(second_lot.lot_no, "YL0002") self.assertIsNone(lot.source_material_sub_batch_no) self.assertEqual(lot.source_material_summary, "客户A") self.assertIsNotNone(balance) self.assertEqual(balance.weight_on_hand_kg, Decimal("75.000000")) def test_dedicated_customer_supplied_inbound_ignores_manual_lot_no_and_starts_from_one(self) -> None: row = create_customer_supplied_inbound( CustomerSuppliedInboundCreate( material_item_id=self.material.id, warehouse_id=self.warehouse.id, provider_name="客户B", inbound_weight_kg=80, lot_no="旧接口手填批次号", ), context=Context(), db=self.db, ) lot = self.db.scalar(select(StockLot).where(StockLot.id == row.lot_id)) self.assertEqual(lot.lot_no, "YL0001") self.assertEqual(lot.source_material_summary, "客户B") if __name__ == "__main__": unittest.main()