from __future__ import annotations

import unittest
from datetime import date, datetime
from decimal import Decimal

from fastapi import HTTPException
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.document_archive  # noqa: E402,F401
import app.models.master_data  # noqa: E402,F401
import app.models.miniapp  # noqa: E402,F401
import app.models.operations  # noqa: E402,F401
import app.models.org  # noqa: E402,F401
import app.models.planning  # noqa: E402,F401
import app.models.sales  # noqa: E402,F401
from app.api.routes.sales import create_delivery, create_sales_order  # noqa: E402
from app.models.base import Base  # noqa: E402
from app.models.document_archive import DocumentArchive  # noqa: E402
from app.models.master_data import Item, StockBalance, Warehouse  # noqa: E402
from app.models.operations import Delivery, DeliveryItem, InventoryTxn, StockLot  # noqa: E402
from app.models.org import Department, Employee, User  # noqa: E402
from app.models.sales import Customer, SalesOrder, SalesOrderItem  # noqa: E402
from app.schemas.database import SalesOrderCreate, SalesOrderItemCreate  # noqa: E402
from app.schemas.operations import DeliveryCreate, DeliveryItemCreate  # noqa: E402
from app.services.auth import AuthContext  # noqa: E402
from app.services.document_archives import ARCHIVE_STATUS_READY, DOCUMENT_TYPE_WAREHOUSE_OPERATION  # noqa: E402
from app.services.operations import get_delivery_items_query  # noqa: E402
from app.services.sales_planning import get_sales_order_query  # noqa: E402


class SalesOrderDeliveryTraceTest(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._seed_master_data()

    def tearDown(self) -> None:
        self.db.close()

    def _seed_master_data(self) -> None:
        self.department = Department(id=1, dept_code="D-SALES", dept_name="销售部", dept_type="SALES", status="ACTIVE")
        self.employee = Employee(id=1, employee_code="EMP-001", employee_name="张三", dept_id=1, status="ACTIVE")
        self.user = User(
            id=1,
            username="tester",
            password_hash="x",
            employee_id=1,
            dept_id=1,
            nickname="测试员",
            is_super_admin=1,
            status="ACTIVE",
        )
        self.customer = Customer(
            id=1,
            customer_code="CUS-001",
            customer_name="五金客户A",
            short_name="客户A",
            contact_name="李工",
            contact_phone="13800000000",
            address="宁波测试路1号",
            credit_days=30,
            status="ACTIVE",
        )
        self.product = Item(
            id=1,
            item_code="FG-001",
            item_name="测试支架",
            item_type="FINISHED_GOOD",
            unit_weight_kg=Decimal("2.000000"),
            safety_stock_weight_kg=Decimal("0"),
            status="ACTIVE",
        )
        self.raw_material = Item(
            id=2,
            item_code="MAT-001",
            item_name="冷轧钢板",
            item_type="RAW_MATERIAL",
            specification="1.0*1000",
            material_grade="SPCC",
            unit_weight_kg=Decimal("1.000000"),
            safety_stock_weight_kg=Decimal("0"),
            status="ACTIVE",
        )
        self.warehouse = Warehouse(id=1, warehouse_code="WH-FG", warehouse_name="成品库", warehouse_type="FINISHED", status="ACTIVE")
        self.db.add_all([self.department, self.employee, self.user, self.customer, self.product, self.raw_material, self.warehouse])
        self.db.flush()

        self.raw_lot = StockLot(
            id=1,
            lot_no="RML-001",
            lot_role="INBOUND_RAW",
            item_id=self.raw_material.id,
            warehouse_id=self.warehouse.id,
            source_doc_type="TEST",
            source_doc_id=1,
            inbound_qty=Decimal("100"),
            inbound_weight_kg=Decimal("100"),
            remaining_qty=Decimal("60"),
            remaining_weight_kg=Decimal("60"),
            locked_qty=Decimal("0"),
            locked_weight_kg=Decimal("0"),
            unit_cost=Decimal("5"),
            quality_status="PASS",
            status="AVAILABLE",
        )
        self.finished_lot = StockLot(
            id=2,
            lot_no="FGL-001",
            parent_lot_id=self.raw_lot.id,
            lot_role="FINISHED_FROM_RAW_BATCH",
            item_id=self.product.id,
            warehouse_id=self.warehouse.id,
            source_doc_type="COMPLETION",
            source_doc_id=1,
            source_line_id=1,
            source_material_lot_id=self.raw_lot.id,
            source_material_sub_batch_no="RML-001",
            source_material_summary="RML-001 / 冷轧钢板 / 40kg / ¥200",
            inbound_qty=Decimal("20"),
            inbound_weight_kg=Decimal("40"),
            remaining_qty=Decimal("20"),
            remaining_weight_kg=Decimal("40"),
            locked_qty=Decimal("0"),
            locked_weight_kg=Decimal("0"),
            unit_cost=Decimal("10"),
            production_date=date.today(),
            quality_status="PASS",
            status="AVAILABLE",
        )
        self.finished_balance = StockBalance(
            id=1,
            item_id=self.product.id,
            warehouse_id=self.warehouse.id,
            location_id=None,
            qty_on_hand=Decimal("20"),
            weight_on_hand_kg=Decimal("40"),
            qty_available=Decimal("20"),
            weight_available_kg=Decimal("40"),
            qty_allocated=Decimal("0"),
            weight_allocated_kg=Decimal("0"),
            avg_unit_cost=Decimal("10"),
        )
        self.db.add_all([self.raw_lot, self.finished_lot, self.finished_balance])
        self.db.commit()

    def _seed_second_finished_lot(self) -> StockLot:
        second_lot = StockLot(
            id=3,
            lot_no="FGL-002",
            parent_lot_id=self.raw_lot.id,
            lot_role="FINISHED_FROM_RAW_BATCH",
            item_id=self.product.id,
            warehouse_id=self.warehouse.id,
            source_doc_type="COMPLETION",
            source_doc_id=2,
            source_line_id=2,
            source_material_lot_id=self.raw_lot.id,
            source_material_sub_batch_no="RML-001",
            source_material_summary="RML-001 / 冷轧钢板 / 10kg / ¥50",
            inbound_qty=Decimal("5"),
            inbound_weight_kg=Decimal("10"),
            remaining_qty=Decimal("5"),
            remaining_weight_kg=Decimal("10"),
            locked_qty=Decimal("0"),
            locked_weight_kg=Decimal("0"),
            unit_cost=Decimal("10"),
            production_date=date.today(),
            quality_status="PASS",
            status="AVAILABLE",
        )
        self.finished_balance.qty_on_hand += Decimal("5")
        self.finished_balance.weight_on_hand_kg += Decimal("10")
        self.finished_balance.qty_available += Decimal("5")
        self.finished_balance.weight_available_kg += Decimal("10")
        self.db.add_all([second_lot, self.finished_balance])
        self.db.commit()
        return second_lot

    def _auth_context(self) -> AuthContext:
        return AuthContext(
            user=self.user,
            employee=self.employee,
            department=self.department,
            role_codes=["ADMIN"],
            role_names=["管理员"],
            permission_codes=[],
        )

    def _seed_employee_permission(
        self,
        permission_code: str,
        permission_name: str,
        module_code: str,
        role_code: str,
        role_name: str,
    ) -> Employee:
        from app.models.org import Permission, Role, RolePermission, UserRole

        now = datetime(2026, 6, 8, 9, 0, 0)
        permission = self.db.scalar(select(Permission).where(Permission.permission_code == permission_code))
        if not permission:
            permission = Permission(
                permission_code=permission_code,
                permission_name=permission_name,
                module_code=module_code,
                action_code="VIEW",
                status="ACTIVE",
                created_at=now,
                updated_at=now,
            )
            self.db.add(permission)
            self.db.flush()

        role = self.db.scalar(select(Role).where(Role.role_code == role_code))
        if not role:
            role = Role(
                role_code=role_code,
                role_name=role_name,
                role_scope="SYSTEM",
                status="ACTIVE",
                remark=None,
                created_at=now,
                updated_at=now,
            )
            self.db.add(role)
            self.db.flush()

        role_permission = self.db.scalar(
            select(RolePermission).where(
                RolePermission.role_id == role.id,
                RolePermission.permission_id == permission.id,
            )
        )
        if not role_permission:
            self.db.add(RolePermission(role_id=role.id, permission_id=permission.id, created_at=now))

        user_role = self.db.scalar(select(UserRole).where(UserRole.user_id == self.user.id, UserRole.role_id == role.id))
        if not user_role:
            self.db.add(UserRole(user_id=self.user.id, role_id=role.id, created_at=now))

        self.db.commit()
        return self.employee

    def _seed_sales_employee_permission(self) -> Employee:
        return self._seed_employee_permission(
            "MENU_SALES_PLANNING",
            "客户名录/销售订单菜单",
            "SALES_PLANNING",
            "SALES",
            "销售人员",
        )

    def _seed_inventory_ledger_employee_permission(self) -> Employee:
        return self._seed_employee_permission(
            "MENU_INVENTORY_LEDGER",
            "嘉恒仓库",
            "INVENTORY_LEDGER",
            "WAREHOUSE",
            "仓库人员",
        )

    def test_create_sales_order_persists_sales_employee(self) -> None:
        sales_employee = self._seed_sales_employee_permission()

        order_result = create_sales_order(
            SalesOrderCreate(
                customer_id=self.customer.id,
                sales_employee_id=sales_employee.id,
                promised_date=date.today(),
                delivery_address=self.customer.address,
                tax_rate=0,
                items=[
                    SalesOrderItemCreate(
                        product_item_id=self.product.id,
                        order_qty=3,
                        unit_price=12.5,
                        customer_part_no="C-PART-001",
                    )
                ],
            ),
            db=self.db,
        )

        order = self.db.get(SalesOrder, order_result.order_id)
        order_row = self.db.execute(get_sales_order_query(limit=10)).mappings().first()
        self.assertEqual(order.sales_employee_id, sales_employee.id)
        self.assertEqual(order_row["sales_employee_id"], sales_employee.id)
        self.assertEqual(order_row["sales_employee_name"], sales_employee.employee_name)

    def test_sales_order_delivery_updates_fulfillment_and_trace_fields(self) -> None:
        sales_employee = self._seed_sales_employee_permission()

        order_result = create_sales_order(
            SalesOrderCreate(
                customer_id=self.customer.id,
                sales_employee_id=sales_employee.id,
                promised_date=date.today(),
                delivery_address=self.customer.address,
                tax_rate=0,
                items=[
                    SalesOrderItemCreate(
                        product_item_id=self.product.id,
                        order_qty=10,
                        unit_price=12.5,
                        customer_part_no="C-PART-001",
                    )
                ],
            ),
            db=self.db,
        )
        order_item = self.db.scalar(select(SalesOrderItem).where(SalesOrderItem.sales_order_id == order_result.order_id))

        create_delivery(
            DeliveryCreate(
                sales_order_id=order_result.order_id,
                customer_id=self.customer.id,
                warehouse_id=self.warehouse.id,
                consignee_name=self.customer.contact_name,
                consignee_phone=self.customer.contact_phone,
                delivery_address=self.customer.address,
                waybill_no="YD-XS-001",
                freight_amount=36.8,
                items=[
                    DeliveryItemCreate(
                        sales_order_item_id=order_item.id,
                        product_item_id=self.product.id,
                        lot_id=self.finished_lot.id,
                        delivery_qty=4,
                        delivery_weight_kg=8,
                        unit_price=12.5,
                    )
                ],
            ),
            context=self._auth_context(),
            db=self.db,
        )

        self.db.refresh(order_item)
        sales_order = self.db.get(SalesOrder, order_result.order_id)
        delivery_item = self.db.scalar(select(DeliveryItem))
        delivery = self.db.scalar(select(Delivery))
        trace_row = self.db.execute(get_delivery_items_query(limit=10)).mappings().first()
        stock_balance = self.db.scalar(select(StockBalance).where(StockBalance.item_id == self.product.id))

        self.assertEqual(delivery.logistics_waybill_no, "YD-XS-001")
        self.assertEqual(float(delivery.logistics_freight_amount), 36.8)
        self.assertEqual(float(order_item.delivered_qty), 4)
        self.assertEqual(order_item.status, "PARTIAL")
        self.assertEqual(sales_order.status, "PARTIAL")
        self.assertEqual(delivery_item.sales_order_item_id, order_item.id)
        self.assertEqual(trace_row["source_material_lot_no"], "RML-001")
        self.assertEqual(trace_row["source_material_code"], "MAT-001")
        self.assertEqual(trace_row["source_material_name"], "冷轧钢板")
        self.assertEqual(trace_row["source_material_sub_batch_no"], "RML-001")
        self.assertEqual(trace_row["lot_no"], "FGL-001")
        self.assertEqual(float(stock_balance.qty_on_hand), 16)
        self.assertEqual(float(stock_balance.qty_available), 16)

    def test_delivery_trace_aliases_legacy_sub_batch_to_source_lot_no(self) -> None:
        self.finished_lot.source_material_lot_id = None
        self.finished_lot.source_material_sub_batch_no = "JH_MAT001_0001"
        self.db.commit()
        sales_employee = self._seed_sales_employee_permission()

        order_result = create_sales_order(
            SalesOrderCreate(
                customer_id=self.customer.id,
                sales_employee_id=sales_employee.id,
                promised_date=date.today(),
                delivery_address=self.customer.address,
                tax_rate=0,
                items=[
                    SalesOrderItemCreate(
                        product_item_id=self.product.id,
                        order_qty=1,
                        unit_price=12.5,
                    )
                ],
            ),
            db=self.db,
        )
        order_item = self.db.scalar(select(SalesOrderItem).where(SalesOrderItem.sales_order_id == order_result.order_id))

        create_delivery(
            DeliveryCreate(
                sales_order_id=order_result.order_id,
                customer_id=self.customer.id,
                warehouse_id=self.warehouse.id,
                consignee_name=self.customer.contact_name,
                consignee_phone=self.customer.contact_phone,
                delivery_address=self.customer.address,
                waybill_no="YD-XS-LEGACY",
                freight_amount=10,
                items=[
                    DeliveryItemCreate(
                        sales_order_item_id=order_item.id,
                        product_item_id=self.product.id,
                        lot_id=self.finished_lot.id,
                        delivery_qty=1,
                        delivery_weight_kg=2,
                        unit_price=12.5,
                    )
                ],
            ),
            context=self._auth_context(),
            db=self.db,
        )

        trace_row = self.db.execute(get_delivery_items_query(limit=10)).mappings().first()

        self.assertEqual(trace_row["source_material_lot_no"], "JH_MAT001_0001")
        self.assertEqual(trace_row["source_material_sub_batch_no"], "JH_MAT001_0001")

    def test_delivery_line_link_infers_sales_order_header(self) -> None:
        sales_employee = self._seed_sales_employee_permission()

        order_result = create_sales_order(
            SalesOrderCreate(
                customer_id=self.customer.id,
                sales_employee_id=sales_employee.id,
                promised_date=date.today(),
                delivery_address=self.customer.address,
                tax_rate=0,
                items=[
                    SalesOrderItemCreate(
                        product_item_id=self.product.id,
                        order_qty=6,
                        unit_price=10,
                    )
                ],
            ),
            db=self.db,
        )
        order_item = self.db.scalar(select(SalesOrderItem).where(SalesOrderItem.sales_order_id == order_result.order_id))

        delivery_result = create_delivery(
            DeliveryCreate(
                customer_id=self.customer.id,
                warehouse_id=self.warehouse.id,
                waybill_no="YD-XS-002",
                freight_amount=20,
                items=[
                    DeliveryItemCreate(
                        sales_order_item_id=order_item.id,
                        product_item_id=self.product.id,
                        lot_id=self.finished_lot.id,
                        delivery_qty=2,
                        delivery_weight_kg=4,
                        unit_price=10,
                    )
                ],
            ),
            context=self._auth_context(),
            db=self.db,
        )

        delivery = self.db.get(Delivery, delivery_result.delivery_id)
        sales_order = self.db.get(SalesOrder, order_result.order_id)
        sales_out_txn = self.db.scalar(select(InventoryTxn).where(InventoryTxn.txn_type == "SALES_OUT"))
        self.assertEqual(delivery_result.sales_order_id, order_result.order_id)
        self.assertIsNotNone(sales_out_txn)
        self.assertEqual(delivery_result.archive_status, ARCHIVE_STATUS_READY)
        self.assertEqual(delivery_result.archive_document_type, DOCUMENT_TYPE_WAREHOUSE_OPERATION)
        self.assertEqual(delivery_result.archive_business_id, sales_out_txn.id)
        archive = self.db.scalar(
            select(DocumentArchive).where(
                DocumentArchive.document_type == DOCUMENT_TYPE_WAREHOUSE_OPERATION,
                DocumentArchive.business_id == sales_out_txn.id,
            )
        )
        self.assertIsNotNone(archive)
        self.assertEqual(archive.status, ARCHIVE_STATUS_READY)
        self.assertEqual(delivery.sales_order_id, order_result.order_id)
        self.assertEqual(sales_order.status, "PARTIAL")

    def test_delivery_generates_archive_for_each_sales_out_transaction(self) -> None:
        second_lot = self._seed_second_finished_lot()
        sales_employee = self._seed_sales_employee_permission()
        order_result = create_sales_order(
            SalesOrderCreate(
                customer_id=self.customer.id,
                sales_employee_id=sales_employee.id,
                promised_date=date.today(),
                delivery_address=self.customer.address,
                tax_rate=0,
                items=[
                    SalesOrderItemCreate(
                        product_item_id=self.product.id,
                        order_qty=6,
                        unit_price=10,
                    )
                ],
            ),
            db=self.db,
        )
        order_item = self.db.scalar(select(SalesOrderItem).where(SalesOrderItem.sales_order_id == order_result.order_id))

        delivery_result = create_delivery(
            DeliveryCreate(
                customer_id=self.customer.id,
                warehouse_id=self.warehouse.id,
                waybill_no="YD-MULTI-LOT",
                freight_amount=20,
                items=[
                    DeliveryItemCreate(
                        sales_order_item_id=order_item.id,
                        product_item_id=self.product.id,
                        lot_id=self.finished_lot.id,
                        delivery_qty=2,
                        delivery_weight_kg=4,
                        unit_price=10,
                    ),
                    DeliveryItemCreate(
                        sales_order_item_id=order_item.id,
                        product_item_id=self.product.id,
                        lot_id=second_lot.id,
                        delivery_qty=1,
                        delivery_weight_kg=2,
                        unit_price=10,
                    ),
                ],
            ),
            context=self._auth_context(),
            db=self.db,
        )

        sales_out_txns = self.db.scalars(
            select(InventoryTxn).where(InventoryTxn.txn_type == "SALES_OUT").order_by(InventoryTxn.id)
        ).all()
        self.assertEqual(len(sales_out_txns), 2)
        self.assertEqual(delivery_result.archive_business_id, sales_out_txns[0].id)
        archive_business_ids = set(
            self.db.scalars(
                select(DocumentArchive.business_id).where(
                    DocumentArchive.document_type == DOCUMENT_TYPE_WAREHOUSE_OPERATION,
                    DocumentArchive.business_id.in_([txn.id for txn in sales_out_txns]),
                )
            ).all()
        )
        self.assertEqual(archive_business_ids, {txn.id for txn in sales_out_txns})

    def test_create_delivery_accepts_inventory_ledger_shipper(self) -> None:
        shipper = self._seed_inventory_ledger_employee_permission()

        delivery_result = create_delivery(
            DeliveryCreate(
                customer_id=self.customer.id,
                warehouse_id=self.warehouse.id,
                shipper_employee_id=shipper.id,
                waybill_no="YD-INVENTORY-SHIPPER",
                freight_amount=12,
                items=[
                    DeliveryItemCreate(
                        product_item_id=self.product.id,
                        lot_id=self.finished_lot.id,
                        delivery_qty=1,
                        delivery_weight_kg=2,
                        unit_price=0,
                    )
                ],
            ),
            context=self._auth_context(),
            db=self.db,
        )

        delivery = self.db.get(Delivery, delivery_result.delivery_id)
        self.assertEqual(delivery.shipper_employee_id, shipper.id)

    def test_sales_delivery_requires_logistics_info(self) -> None:
        sales_employee = self._seed_sales_employee_permission()

        order_result = create_sales_order(
            SalesOrderCreate(
                customer_id=self.customer.id,
                sales_employee_id=sales_employee.id,
                promised_date=date.today(),
                delivery_address=self.customer.address,
                tax_rate=0,
                items=[
                    SalesOrderItemCreate(
                        product_item_id=self.product.id,
                        order_qty=6,
                        unit_price=10,
                    )
                ],
            ),
            db=self.db,
        )
        order_item = self.db.scalar(select(SalesOrderItem).where(SalesOrderItem.sales_order_id == order_result.order_id))

        with self.assertRaises(HTTPException) as exc:
            create_delivery(
                DeliveryCreate(
                    customer_id=self.customer.id,
                    warehouse_id=self.warehouse.id,
                    items=[
                        DeliveryItemCreate(
                            sales_order_item_id=order_item.id,
                            product_item_id=self.product.id,
                            lot_id=self.finished_lot.id,
                            delivery_qty=2,
                            delivery_weight_kg=4,
                            unit_price=10,
                        )
                    ],
                ),
                context=self._auth_context(),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 400)
        self.assertIn("运单号", exc.exception.detail)
        self.assertIn("运费", exc.exception.detail)

    def test_sales_delivery_allows_order_photo_instead_of_waybill(self) -> None:
        sales_employee = self._seed_sales_employee_permission()

        order_result = create_sales_order(
            SalesOrderCreate(
                customer_id=self.customer.id,
                sales_employee_id=sales_employee.id,
                promised_date=date.today(),
                delivery_address=self.customer.address,
                tax_rate=0,
                items=[
                    SalesOrderItemCreate(
                        product_item_id=self.product.id,
                        order_qty=6,
                        unit_price=10,
                    )
                ],
            ),
            db=self.db,
        )
        order_item = self.db.scalar(select(SalesOrderItem).where(SalesOrderItem.sales_order_id == order_result.order_id))

        delivery_result = create_delivery(
            DeliveryCreate(
                customer_id=self.customer.id,
                warehouse_id=self.warehouse.id,
                order_photo_url="/inventory/logistics-photos/sales-photo.jpg",
                freight_amount=20,
                items=[
                    DeliveryItemCreate(
                        sales_order_item_id=order_item.id,
                        product_item_id=self.product.id,
                        lot_id=self.finished_lot.id,
                        delivery_qty=2,
                        delivery_weight_kg=4,
                        unit_price=10,
                    )
                ],
            ),
            context=self._auth_context(),
            db=self.db,
        )

        delivery = self.db.get(Delivery, delivery_result.delivery_id)
        txn = self.db.scalar(select(InventoryTxn).where(InventoryTxn.txn_type == "SALES_OUT"))
        self.assertIsNone(delivery.logistics_waybill_no)
        self.assertEqual(delivery.logistics_photo_url, "/inventory/logistics-photos/sales-photo.jpg")
        self.assertEqual(txn.logistics_photo_url, "/inventory/logistics-photos/sales-photo.jpg")

    def test_direct_customer_delivery_without_sales_order_preserves_trace_and_inventory(self) -> None:
        delivery_result = create_delivery(
            DeliveryCreate(
                customer_id=self.customer.id,
                warehouse_id=self.warehouse.id,
                consignee_name=self.customer.contact_name,
                consignee_phone=self.customer.contact_phone,
                delivery_address=self.customer.address,
                waybill_no="YD-DIRECT-001",
                freight_amount=18,
                items=[
                    DeliveryItemCreate(
                        sales_order_item_id=None,
                        product_item_id=self.product.id,
                        lot_id=self.finished_lot.id,
                        delivery_qty=3,
                        delivery_weight_kg=6,
                        unit_price=0,
                    )
                ],
            ),
            context=self._auth_context(),
            db=self.db,
        )

        delivery = self.db.get(Delivery, delivery_result.delivery_id)
        delivery_item = self.db.scalar(select(DeliveryItem).where(DeliveryItem.delivery_id == delivery.id))
        trace_row = self.db.execute(get_delivery_items_query(limit=10, delivery_id=delivery.id)).mappings().first()
        stock_balance = self.db.scalar(select(StockBalance).where(StockBalance.item_id == self.product.id))
        sales_out_txn = self.db.scalar(select(InventoryTxn).where(InventoryTxn.txn_type == "SALES_OUT"))

        self.assertIsNone(delivery.sales_order_id)
        self.assertEqual(delivery.customer_id, self.customer.id)
        self.assertIsNone(delivery_item.sales_order_item_id)
        self.assertEqual(trace_row["source_material_lot_no"], "RML-001")
        self.assertEqual(trace_row["source_material_code"], "MAT-001")
        self.assertEqual(trace_row["source_material_name"], "冷轧钢板")
        self.assertEqual(trace_row["source_material_sub_batch_no"], "RML-001")
        self.assertEqual(trace_row["source_material_summary"], "RML-001 / 冷轧钢板 / 40kg / ¥200")
        self.assertEqual(float(stock_balance.qty_on_hand), 17)
        self.assertEqual(float(stock_balance.qty_available), 17)
        self.assertEqual(sales_out_txn.source_doc_type, "DELIVERY")
        self.assertEqual(sales_out_txn.source_doc_id, delivery.id)

    def test_sales_delivery_rejects_non_finished_warehouse(self) -> None:
        raw_warehouse = Warehouse(
            id=2,
            warehouse_code="WH-RAW",
            warehouse_name="原材料库",
            warehouse_type="RAW",
            status="ACTIVE",
        )
        self.db.add(raw_warehouse)
        self.db.commit()

        with self.assertRaises(HTTPException) as exc:
            create_delivery(
                DeliveryCreate(
                    customer_id=self.customer.id,
                    warehouse_id=raw_warehouse.id,
                    waybill_no="YD-WRONG-WH",
                    freight_amount=12,
                    items=[
                        DeliveryItemCreate(
                            product_item_id=self.product.id,
                            lot_id=self.finished_lot.id,
                            delivery_qty=1,
                            delivery_weight_kg=2,
                            unit_price=0,
                        )
                    ],
                ),
                context=self._auth_context(),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 400)
        self.assertIn("销售出库必须选择成品库", exc.exception.detail)


if __name__ == "__main__":
    unittest.main()
