from __future__ import annotations

import unittest
from datetime import UTC, datetime, timedelta
from io import BytesIO

from openpyxl import Workbook
from openpyxl import load_workbook
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.operations  # noqa: E402,F401
import app.models.org  # noqa: E402,F401
import app.models.sales  # noqa: E402,F401
from app.models.base import Base  # noqa: E402
from app.models.document_archive import DocumentArchive  # noqa: E402
from app.models.master_data import Warehouse  # noqa: E402
from app.services.document_archives import ARCHIVE_STATUS_READY, DOCUMENT_TYPE_WAREHOUSE_OPERATION  # noqa: E402


class ReturnWarehouseFlowTest(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()

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

    def seed_delivery_trace(self) -> dict[str, int]:
        from decimal import Decimal

        from app.models.master_data import Item
        from app.models.master_data import Bom
        from app.models.org import Department, Employee
        from app.models.operations import (
            Delivery,
            DeliveryItem,
            ProcessRoute,
            ProcessRouteOperation,
            StockLot,
            WarehouseLocation,
            WorkCenter,
        )
        from app.models.sales import Customer, SalesOrder, SalesOrderItem

        now = datetime.now(UTC)
        department = Department(
            id=1,
            dept_code="D-001",
            dept_name="生产部",
            dept_type="PRODUCTION",
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        employee = Employee(
            id=1,
            employee_code="EMP-001",
            employee_name="返工员工",
            dept_id=1,
            mobile="13800000001",
            is_operator=1,
            is_workshop_staff=1,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        customer = Customer(id=1, customer_code="CUS-001", customer_name="测试客户", short_name="测试客户", status="ACTIVE")
        return_wh = Warehouse(
            id=1,
            warehouse_code="WH-RETURN-01",
            warehouse_name="退货库",
            warehouse_type="RETURN",
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        finished_wh = Warehouse(
            id=2,
            warehouse_code="WH-FG-01",
            warehouse_name="成品库",
            warehouse_type="FINISHED",
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        scrap_wh = Warehouse(
            id=3,
            warehouse_code="WH-SCRAP-01",
            warehouse_name="废料库",
            warehouse_type="SCRAP",
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        return_loc = WarehouseLocation(
            id=1,
            warehouse_id=1,
            location_code="RTN-F-01",
            location_name="退货暂存位",
            is_default=1,
            is_locked=0,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        scrap_loc = WarehouseLocation(
            id=2,
            warehouse_id=3,
            location_code="SCRAP-01",
            location_name="废料暂存位",
            is_default=1,
            is_locked=0,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        material = Item(
            id=1,
            item_code="原材料00001",
            item_name="冷轧钢板",
            item_type="RAW_MATERIAL",
            unit_weight_kg=Decimal("1"),
            scrap_sale_price=Decimal("1.2"),
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        product = Item(
            id=2,
            item_code="产品需规00001",
            item_name="冲压支架",
            item_type="FINISHED_GOOD",
            unit_weight_kg=Decimal("2.5"),
            scrap_sale_price=Decimal("1.8"),
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        sales_order = SalesOrder(
            id=1,
            order_no="SO-20260531-001",
            customer_id=1,
            order_date=now.date(),
            total_amount=360,
            status="POSTED",
            created_at=now,
            updated_at=now,
        )
        sales_order_item = SalesOrderItem(
            id=1,
            sales_order_id=1,
            line_no=1,
            product_item_id=2,
            order_qty=30,
            delivered_qty=30,
            unit_price=12,
            line_amount=360,
            status="POSTED",
            created_at=now,
            updated_at=now,
        )
        material_lot = StockLot(
            id=1,
            lot_no="JH_原材料00001_0001",
            lot_role="INBOUND_RAW",
            item_id=1,
            warehouse_id=2,
            location_id=None,
            source_doc_type="OPENING_STOCK",
            source_doc_id=1,
            inbound_qty=0,
            inbound_weight_kg=500,
            remaining_qty=0,
            remaining_weight_kg=0,
            locked_qty=0,
            locked_weight_kg=0,
            unit_cost=5,
            quality_status="PASS",
            status="DEPLETED",
            created_at=now,
            updated_at=now,
        )
        finished_lot = StockLot(
            id=2,
            lot_no="FG-20260531-0001",
            lot_role="INBOUND_FINISHED",
            item_id=2,
            warehouse_id=2,
            location_id=None,
            source_doc_type="FG_RECEIPT",
            source_doc_id=1,
            source_line_id=1,
            source_material_lot_id=1,
            source_material_sub_batch_no="JH_原材料00001_0001-S01",
            source_material_summary="冷轧钢板/JH_原材料00001_0001-S01",
            inbound_qty=100,
            inbound_weight_kg=250,
            remaining_qty=70,
            remaining_weight_kg=175,
            locked_qty=0,
            locked_weight_kg=0,
            unit_cost=8,
            quality_status="PASS",
            status="AVAILABLE",
            created_at=now,
            updated_at=now,
        )
        delivery = Delivery(
            id=1,
            delivery_no="发货2026-00001",
            sales_order_id=1,
            customer_id=1,
            warehouse_id=2,
            delivery_date=now,
            status="POSTED",
            created_at=now,
            updated_at=now,
        )
        delivery_item = DeliveryItem(
            id=1,
            delivery_id=1,
            line_no=1,
            sales_order_item_id=1,
            product_item_id=2,
            lot_id=2,
            delivery_qty=30,
            delivery_weight_kg=75,
            unit_price=12,
            line_amount=360,
            status="POSTED",
            created_at=now,
            updated_at=now,
        )
        bom = Bom(
            id=1,
            bom_code="BOM-001",
            product_item_id=2,
            version_no="V1.0",
            is_default=1,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        work_center = WorkCenter(
            id=1,
            center_code="WC-001",
            center_name="返工工位",
            dept_id=1,
            center_type="REWORK",
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        route = ProcessRoute(
            id=1,
            route_code="RT-001",
            route_name="返工路线",
            product_item_id=2,
            version_no="V1.0",
            is_default=1,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        route_operation = ProcessRouteOperation(
            id=1,
            route_id=1,
            seq_no=1,
            process_id=1,
            work_center_id=1,
            operation_name="返工",
            std_setup_minutes=0,
            std_cycle_seconds=0,
            std_labor_cost=0,
            std_machine_cost=0,
            std_outsource_cost=0,
            allowed_scrap_rate=0,
            report_required=1,
            is_key_operation=1,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        self.db.add_all(
            [
                customer,
                department,
                employee,
                return_wh,
                finished_wh,
                scrap_wh,
                return_loc,
                scrap_loc,
                material,
                product,
                sales_order,
                sales_order_item,
                material_lot,
                finished_lot,
                delivery,
                delivery_item,
                bom,
                work_center,
                route,
                route_operation,
            ]
        )
        self.db.commit()
        return {
            "customer_id": 1,
            "return_warehouse_id": 1,
            "finished_warehouse_id": 2,
            "scrap_warehouse_id": 3,
            "product_item_id": 2,
            "material_lot_id": 1,
            "finished_lot_id": 2,
            "delivery_id": 1,
            "delivery_item_id": 1,
            "sales_order_id": 1,
            "sales_order_item_id": 1,
        }

    def test_jiaheng_warehouses_include_return_warehouse(self) -> None:
        from app.api.routes.master_data import _ensure_jiaheng_warehouses

        _ensure_jiaheng_warehouses(self.db)

        warehouse = self.db.scalar(select(Warehouse).where(Warehouse.warehouse_type == "RETURN"))
        self.assertIsNotNone(warehouse)
        self.assertEqual(warehouse.warehouse_code, "WH-RETURN-01")
        self.assertEqual(warehouse.warehouse_name, "退货库")

    def test_return_opening_template_exports_sample_rows_with_quantity(self) -> None:
        from app.api.routes.inventory import build_opening_inventory_template

        workbook = build_opening_inventory_template(self.db, "RETURN")
        output = BytesIO()
        workbook.save(output)
        loaded = load_workbook(BytesIO(output.getvalue()), read_only=True, data_only=True)
        rows = list(loaded["退货库期初导入"].iter_rows(values_only=True))
        headers = list(rows[0])

        self.assertIn("数量", headers)
        self.assertEqual(rows[1][headers.index("仓库类型")], "退货库")
        self.assertEqual(rows[1][headers.index("仓库名称")], "退货库")
        self.assertIn("示例-RETURN-", rows[1][headers.index("批次号")])
        self.assertGreater(rows[1][headers.index("数量")], 0)
        self.assertGreater(rows[1][headers.index("重量(kg)")], 0)

    def test_return_opening_import_requires_positive_quantity(self) -> None:
        from app.api.routes.inventory import OPENING_INVENTORY_HEADERS, import_opening_inventory_workbook
        from app.models.master_data import Item
        from app.models.operations import WarehouseLocation

        now = datetime.now(UTC)
        warehouse = Warehouse(
            id=1,
            warehouse_code="WH-RETURN-01",
            warehouse_name="退货库",
            warehouse_type="RETURN",
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        location = WarehouseLocation(
            id=1,
            warehouse_id=1,
            location_code="RTN-F-01",
            location_name="退货暂存位",
            is_default=1,
            is_locked=0,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        product = Item(
            id=1,
            item_code="产品需规00001",
            item_name="返工待处理五金支架",
            item_type="FINISHED_GOOD",
            unit_weight_kg=1,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        self.db.add_all([warehouse, location, product])
        self.db.commit()
        workbook = Workbook()
        worksheet = workbook.active
        worksheet.title = "退货库期初导入"
        worksheet.append(OPENING_INVENTORY_HEADERS)
        worksheet.append(["退货库", "退货库", "退货暂存位", "产品需规00001", "返工待处理五金支架", "RETURN-OPEN-001", 0, 50, 12.5, "历史退货", "数量为空不允许"])
        output = BytesIO()
        workbook.save(output)

        with self.assertRaises(ValueError) as exc:
            import_opening_inventory_workbook(self.db, "RETURN", output.getvalue(), user_id=1)

        self.assertIn("退货库数量必须大于0", str(exc.exception))

    def test_return_opening_import_rejects_raw_material_items(self) -> None:
        from app.api.routes.inventory import OPENING_INVENTORY_HEADERS, import_opening_inventory_workbook
        from app.models.master_data import Item
        from app.models.operations import WarehouseLocation

        now = datetime.now(UTC)
        warehouse = Warehouse(
            id=1,
            warehouse_code="WH-RETURN-01",
            warehouse_name="退货库",
            warehouse_type="RETURN",
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        location = WarehouseLocation(
            id=1,
            warehouse_id=1,
            location_code="RTN-F-01",
            location_name="退货暂存位",
            is_default=1,
            is_locked=0,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        raw_material = Item(
            id=1,
            item_code="原材料00001",
            item_name="冷轧钢板",
            item_type="RAW_MATERIAL",
            unit_weight_kg=1,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        self.db.add_all([warehouse, location, raw_material])
        self.db.commit()
        workbook = Workbook()
        worksheet = workbook.active
        worksheet.title = "退货库期初导入"
        worksheet.append(OPENING_INVENTORY_HEADERS)
        worksheet.append(["退货库", "退货库", "退货暂存位", "原材料00001", "冷轧钢板", "RETURN-RAW-001", 1, 50, 12.5, "历史退货", "原材料不允许进入退货库"])
        output = BytesIO()
        workbook.save(output)

        with self.assertRaises(Exception) as exc:
            import_opening_inventory_workbook(self.db, "RETURN", output.getvalue(), user_id=1)

        self.assertIn("退货库只能选择客户退回且可返工的产品类物料", str(exc.exception))

    def test_return_stocktake_is_not_weight_only_even_for_dirty_raw_item(self) -> None:
        from app.services.stocktake import stocktake_weight_only

        self.assertFalse(stocktake_weight_only("RETURN", "RAW_MATERIAL"))

    def test_return_item_query_exposes_delivery_and_source_material_trace(self) -> None:
        from decimal import Decimal

        from app.models.master_data import Item
        from app.models.operations import Delivery, DeliveryItem, ReturnItem, ReturnOrder, StockLot, WarehouseLocation
        from app.models.sales import Customer
        from app.services.operations import get_return_items_query

        now = datetime.now(UTC)
        customer = Customer(id=1, customer_code="CUS-001", customer_name="测试客户", status="ACTIVE")
        return_wh = Warehouse(
            id=1,
            warehouse_code="WH-RETURN-01",
            warehouse_name="退货库",
            warehouse_type="RETURN",
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        finished_wh = Warehouse(
            id=2,
            warehouse_code="WH-FG-01",
            warehouse_name="成品库",
            warehouse_type="FINISHED",
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        return_loc = WarehouseLocation(
            id=1,
            warehouse_id=1,
            location_code="RTN-F-01",
            location_name="退货暂存位",
            is_default=1,
            is_locked=0,
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        material = Item(
            id=1,
            item_code="原材料00001",
            item_name="冷轧钢板",
            item_type="RAW_MATERIAL",
            unit_weight_kg=Decimal("1"),
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        product = Item(
            id=2,
            item_code="产品需规00001",
            item_name="冲压支架",
            item_type="FINISHED_GOOD",
            unit_weight_kg=Decimal("2.5"),
            status="ACTIVE",
            created_at=now,
            updated_at=now,
        )
        material_lot = StockLot(
            id=1,
            lot_no="JH_原材料00001_0001",
            lot_role="INBOUND_RAW",
            item_id=1,
            warehouse_id=2,
            location_id=None,
            source_doc_type="OPENING_STOCK",
            source_doc_id=1,
            inbound_qty=0,
            inbound_weight_kg=500,
            remaining_qty=0,
            remaining_weight_kg=0,
            locked_qty=0,
            locked_weight_kg=0,
            unit_cost=5,
            quality_status="PASS",
            status="DEPLETED",
            created_at=now,
            updated_at=now,
        )
        finished_lot = StockLot(
            id=2,
            lot_no="FG-20260531-0001",
            lot_role="INBOUND_FINISHED",
            item_id=2,
            warehouse_id=2,
            location_id=None,
            source_doc_type="FG_RECEIPT",
            source_doc_id=1,
            source_line_id=1,
            source_material_lot_id=1,
            source_material_sub_batch_no="JH_原材料00001_0001-S01",
            source_material_summary="冷轧钢板",
            inbound_qty=100,
            inbound_weight_kg=250,
            remaining_qty=0,
            remaining_weight_kg=0,
            locked_qty=0,
            locked_weight_kg=0,
            unit_cost=8,
            quality_status="PASS",
            status="DEPLETED",
            created_at=now,
            updated_at=now,
        )
        delivery = Delivery(
            id=1,
            delivery_no="发货2026-00001",
            customer_id=1,
            warehouse_id=2,
            delivery_date=now,
            status="POSTED",
            created_at=now,
            updated_at=now,
        )
        delivery_item = DeliveryItem(
            id=1,
            delivery_id=1,
            line_no=1,
            product_item_id=2,
            lot_id=2,
            delivery_qty=30,
            delivery_weight_kg=75,
            unit_price=12,
            line_amount=360,
            status="POSTED",
            created_at=now,
            updated_at=now,
        )
        return_order = ReturnOrder(
            id=1,
            return_no="RTN-001",
            customer_id=1,
            delivery_id=1,
            return_date=now,
            status="CREATED",
            created_at=now,
            updated_at=now,
        )
        return_item = ReturnItem(
            id=1,
            return_order_id=1,
            line_no=1,
            delivery_item_id=1,
            product_item_id=2,
            source_lot_id=2,
            return_qty=5,
            return_weight_kg=Decimal("12.5"),
            disposition_status="PENDING",
            created_at=now,
            updated_at=now,
        )
        self.db.add_all(
            [
                customer,
                return_wh,
                finished_wh,
                return_loc,
                material,
                product,
                material_lot,
                finished_lot,
                delivery,
                delivery_item,
                return_order,
                return_item,
            ]
        )
        self.db.commit()

        row = self.db.execute(get_return_items_query(limit=10)).mappings().first()

        self.assertEqual(row["delivery_item_id"], 1)
        self.assertEqual(row["delivery_no"], "发货2026-00001")
        self.assertEqual(row["source_lot_no"], "FG-20260531-0001")
        self.assertEqual(row["source_material_lot_no"], "JH_原材料00001_0001")
        self.assertEqual(row["source_material_sub_batch_no"], "JH_原材料00001_0001")
        self.assertEqual(row["source_material_name"], "冷轧钢板")

    def test_return_inbound_requires_delivery_item_and_creates_locked_return_lot(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_order
        from app.models.operations import InventoryTxn, ReturnItem, StockLot
        from app.schemas.operations import ReturnItemCreate, ReturnOrderCreate
        from fastapi import HTTPException

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()

        with self.assertRaises(HTTPException) as exc:
            create_return_order(
                ReturnOrderCreate(
                    customer_id=seeded["customer_id"],
                    delivery_id=seeded["delivery_id"],
                    warehouse_id=seeded["return_warehouse_id"],
                    items=[
                        ReturnItemCreate(
                            product_item_id=seeded["product_item_id"],
                            return_qty=5,
                            return_weight_kg=Decimal("12.5"),
                        )
                    ],
                ),
                context=Context(),
                db=self.db,
            )
        self.assertEqual(exc.exception.status_code, 400)
        self.assertEqual(exc.exception.detail, "退货入库必须选择发货批次")

        result = create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="客户反馈尺寸异常，可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=5,
                        return_weight_kg=Decimal("12.5"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )

        return_item = self.db.scalar(select(ReturnItem))
        return_lot = self.db.scalar(select(StockLot).where(StockLot.lot_role == "RETURNED_FINISHED"))
        return_in_txn = self.db.scalar(select(InventoryTxn).where(InventoryTxn.txn_type == "RETURN_IN"))

        self.assertEqual(result.archive_status, ARCHIVE_STATUS_READY)
        self.assertEqual(result.archive_document_type, DOCUMENT_TYPE_WAREHOUSE_OPERATION)
        self.assertIsNotNone(return_in_txn)
        self.assertEqual(result.archive_business_id, return_in_txn.id)
        archive = self.db.scalar(
            select(DocumentArchive).where(
                DocumentArchive.document_type == DOCUMENT_TYPE_WAREHOUSE_OPERATION,
                DocumentArchive.business_id == return_in_txn.id,
            )
        )
        self.assertIsNotNone(archive)
        self.assertEqual(archive.status, ARCHIVE_STATUS_READY)
        self.assertIsNotNone(return_item)
        self.assertEqual(return_item.delivery_item_id, seeded["delivery_item_id"])
        self.assertEqual(return_item.sales_order_item_id, seeded["sales_order_item_id"])
        self.assertEqual(return_item.source_lot_id, seeded["finished_lot_id"])
        self.assertIsNotNone(return_lot)
        self.assertEqual(return_lot.parent_lot_id, seeded["finished_lot_id"])
        self.assertEqual(return_lot.source_material_lot_id, seeded["material_lot_id"])
        self.assertEqual(return_lot.source_material_sub_batch_no, "JH_原材料00001_0001-S01")
        self.assertEqual(return_lot.source_material_summary, "冷轧钢板/JH_原材料00001_0001-S01")
        self.assertEqual(return_lot.status, "LOCKED")
        self.assertEqual(return_lot.quality_status, "PENDING")

    def test_return_inbound_rejects_wrong_warehouse_and_over_return(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_order
        from app.models.master_data import Warehouse
        from app.schemas.operations import ReturnItemCreate, ReturnOrderCreate
        from fastapi import HTTPException

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()

        with self.assertRaises(HTTPException) as wrong_warehouse:
            create_return_order(
                ReturnOrderCreate(
                    customer_id=seeded["customer_id"],
                    delivery_id=seeded["delivery_id"],
                    warehouse_id=seeded["scrap_warehouse_id"],
                    items=[
                        ReturnItemCreate(
                            delivery_item_id=seeded["delivery_item_id"],
                            product_item_id=seeded["product_item_id"],
                            return_qty=1,
                            return_weight_kg=Decimal("2.5"),
                        )
                    ],
                ),
                context=Context(),
                db=self.db,
            )
        self.assertEqual(wrong_warehouse.exception.status_code, 400)
        self.assertIn("退货入库只能进入退货库", wrong_warehouse.exception.detail)

        with self.assertRaises(HTTPException) as negative_qty:
            create_return_order(
                ReturnOrderCreate(
                    customer_id=seeded["customer_id"],
                    delivery_id=seeded["delivery_id"],
                    warehouse_id=seeded["return_warehouse_id"],
                    items=[
                        ReturnItemCreate(
                            delivery_item_id=seeded["delivery_item_id"],
                            product_item_id=seeded["product_item_id"],
                            return_qty=-1,
                            return_weight_kg=Decimal("2.5"),
                        )
                    ],
                ),
                context=Context(),
                db=self.db,
            )
        self.assertEqual(negative_qty.exception.status_code, 400)
        self.assertIn("退货数量必须大于0", negative_qty.exception.detail)

        with self.assertRaises(HTTPException) as over_return:
            create_return_order(
                ReturnOrderCreate(
                    customer_id=seeded["customer_id"],
                    delivery_id=seeded["delivery_id"],
                    warehouse_id=seeded["return_warehouse_id"],
                    items=[
                        ReturnItemCreate(
                            delivery_item_id=seeded["delivery_item_id"],
                            product_item_id=seeded["product_item_id"],
                            return_qty=31,
                            return_weight_kg=Decimal("77.5"),
                        )
                    ],
                ),
                context=Context(),
                db=self.db,
            )
        self.assertEqual(over_return.exception.status_code, 400)
        self.assertIn("超过发货批次可退数量", over_return.exception.detail)

    def test_return_inbound_rejects_inactive_return_warehouse(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_order
        from app.schemas.operations import ReturnItemCreate, ReturnOrderCreate
        from fastapi import HTTPException

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        warehouse = self.db.get(Warehouse, seeded["return_warehouse_id"])
        warehouse.status = "INACTIVE"
        self.db.add(warehouse)
        self.db.commit()

        with self.assertRaises(HTTPException) as exc:
            create_return_order(
                ReturnOrderCreate(
                    customer_id=seeded["customer_id"],
                    delivery_id=seeded["delivery_id"],
                    warehouse_id=seeded["return_warehouse_id"],
                    items=[
                        ReturnItemCreate(
                            delivery_item_id=seeded["delivery_item_id"],
                            product_item_id=seeded["product_item_id"],
                            return_qty=1,
                            return_weight_kg=Decimal("2.5"),
                        )
                    ],
                ),
                context=Context(),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 400)
        self.assertIn("退货仓未启用", exc.exception.detail)

    def test_return_scrap_inbound_goes_directly_to_scrap_warehouse(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_scrap_inbound
        from app.models.master_data import StockBalance
        from app.models.operations import InventoryTxn, ReturnDisposition, ReturnItem, ReturnOrder, StockLot
        from app.schemas.operations import ReturnScrapInboundCreate, ReturnScrapInboundItemCreate

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()

        result = create_return_scrap_inbound(
            ReturnScrapInboundCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["scrap_warehouse_id"],
                return_reason="客户退回判定报废",
                items=[
                    ReturnScrapInboundItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        scrap_weight_kg=Decimal("25"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )

        return_order = self.db.scalar(select(ReturnOrder))
        return_item = self.db.scalar(select(ReturnItem))
        scrap_lot = self.db.scalar(select(StockLot).where(StockLot.lot_role == "CUSTOMER_RETURN_SCRAP"))
        balance = self.db.scalar(select(StockBalance).where(StockBalance.warehouse_id == seeded["scrap_warehouse_id"]))
        txn = self.db.scalar(select(InventoryTxn).where(InventoryTxn.txn_type == "RETURN_SCRAP_IN"))
        disposition = self.db.scalar(select(ReturnDisposition))

        self.assertIsNotNone(return_order)
        self.assertEqual(result.archive_status, ARCHIVE_STATUS_READY)
        self.assertEqual(result.archive_document_type, DOCUMENT_TYPE_WAREHOUSE_OPERATION)
        self.assertIsNotNone(txn)
        self.assertEqual(result.archive_business_id, txn.id)
        archive = self.db.scalar(
            select(DocumentArchive).where(
                DocumentArchive.document_type == DOCUMENT_TYPE_WAREHOUSE_OPERATION,
                DocumentArchive.business_id == txn.id,
            )
        )
        self.assertIsNotNone(archive)
        self.assertEqual(archive.status, ARCHIVE_STATUS_READY)
        self.assertEqual(return_order.status, "CLOSED")
        self.assertEqual(return_item.disposition_status, "CLOSED")
        self.assertEqual(return_item.return_qty, Decimal("10.000000"))
        self.assertEqual(return_item.return_weight_kg, Decimal("25.000000"))
        self.assertEqual(scrap_lot.warehouse_id, seeded["scrap_warehouse_id"])
        self.assertEqual(scrap_lot.parent_lot_id, seeded["finished_lot_id"])
        self.assertEqual(scrap_lot.source_material_lot_id, seeded["material_lot_id"])
        self.assertEqual(scrap_lot.source_material_sub_batch_no, "JH_原材料00001_0001-S01")
        self.assertEqual(scrap_lot.quality_status, "SCRAP")
        self.assertEqual(scrap_lot.status, "AVAILABLE")
        self.assertEqual(balance.qty_on_hand, Decimal("0.000000"))
        self.assertEqual(balance.qty_available, Decimal("0.000000"))
        self.assertEqual(balance.weight_on_hand_kg, Decimal("25.000000"))
        self.assertEqual(txn.lot_id, scrap_lot.id)
        self.assertEqual(txn.weight_change_kg, Decimal("25.000000"))
        self.assertEqual(disposition.return_item_id, return_item.id)
        self.assertEqual(disposition.disposition_type, "SCRAP")
        self.assertEqual(disposition.status, "CLOSED")
        self.assertEqual(disposition.scrap_weight_kg, Decimal("25.000000"))
        self.assertEqual(disposition.scrap_sale_amount, Decimal("45.00"))

    def test_return_scrap_inbound_rejects_over_return_weight(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_scrap_inbound
        from app.schemas.operations import ReturnScrapInboundCreate, ReturnScrapInboundItemCreate
        from fastapi import HTTPException

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()

        with self.assertRaises(HTTPException) as exc:
            create_return_scrap_inbound(
                ReturnScrapInboundCreate(
                    customer_id=seeded["customer_id"],
                    delivery_id=seeded["delivery_id"],
                    warehouse_id=seeded["scrap_warehouse_id"],
                    return_reason="客户退回判定报废",
                    items=[
                        ReturnScrapInboundItemCreate(
                            delivery_item_id=seeded["delivery_item_id"],
                            product_item_id=seeded["product_item_id"],
                            scrap_qty=30,
                            scrap_weight_kg=Decimal("80"),
                        )
                    ],
                ),
                context=Context(),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 400)
        self.assertIn("超过发货批次可退重量", exc.exception.detail)

    def test_return_scrap_inbound_rejects_inactive_scrap_warehouse(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_scrap_inbound
        from app.schemas.operations import ReturnScrapInboundCreate, ReturnScrapInboundItemCreate
        from fastapi import HTTPException

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        warehouse = self.db.get(Warehouse, seeded["scrap_warehouse_id"])
        warehouse.status = "INACTIVE"
        self.db.add(warehouse)
        self.db.commit()

        with self.assertRaises(HTTPException) as exc:
            create_return_scrap_inbound(
                ReturnScrapInboundCreate(
                    customer_id=seeded["customer_id"],
                    delivery_id=seeded["delivery_id"],
                    warehouse_id=seeded["scrap_warehouse_id"],
                    items=[
                        ReturnScrapInboundItemCreate(
                            delivery_item_id=seeded["delivery_item_id"],
                            product_item_id=seeded["product_item_id"],
                            scrap_qty=1,
                            scrap_weight_kg=Decimal("2.5"),
                        )
                    ],
                ),
                context=Context(),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 400)
        self.assertIn("废料仓未启用", exc.exception.detail)

    def test_return_disposition_rejects_scrap_after_scrap_moved_to_scrap_warehouse(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_disposition, create_return_order
        from app.schemas.operations import ReturnDispositionCreate, ReturnItemCreate, ReturnOrderCreate
        from fastapi import HTTPException

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="客户反馈尺寸异常，可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=5,
                        return_weight_kg=Decimal("12.5"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )

        with self.assertRaises(HTTPException) as exc:
            create_return_disposition(
                ReturnDispositionCreate(return_item_id=1, disposition_type="SCRAP", scrap_weight_kg=Decimal("12.5")),
                context=Context(),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 400)
        self.assertIn("退货库处置只支持返工", exc.exception.detail)

    def test_return_rework_outbound_deducts_return_lot_and_creates_rework_order(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_order, create_return_rework_outbound
        from app.models.master_data import StockBalance
        from app.models.operations import InventoryTxn, ReturnDisposition, StockLot
        from app.schemas.operations import ReturnDispositionCreate, ReturnItemCreate, ReturnOrderCreate

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=3,
                        return_weight_kg=Decimal("7.5"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )

        result = create_return_rework_outbound(
            ReturnDispositionCreate(return_item_id=1, disposition_type="REWORK", extra_cost_amount=18, remark="返工出库"),
            context=Context(),
            db=self.db,
        )
        return_lot = self.db.scalar(select(StockLot).where(StockLot.lot_role == "RETURNED_FINISHED"))
        balance = self.db.scalar(select(StockBalance).where(StockBalance.warehouse_id == seeded["return_warehouse_id"]))
        txn = self.db.scalar(select(InventoryTxn).where(InventoryTxn.txn_type == "RETURN_OUT"))
        disposition = self.db.scalar(select(ReturnDisposition))

        self.assertEqual(result.disposition_type, "REWORK")
        self.assertEqual(result.archive_status, ARCHIVE_STATUS_READY)
        self.assertEqual(result.archive_document_type, DOCUMENT_TYPE_WAREHOUSE_OPERATION)
        self.assertIsNotNone(txn)
        self.assertEqual(result.archive_business_id, txn.id)
        archive = self.db.scalar(
            select(DocumentArchive).where(
                DocumentArchive.document_type == DOCUMENT_TYPE_WAREHOUSE_OPERATION,
                DocumentArchive.business_id == txn.id,
            )
        )
        self.assertIsNotNone(archive)
        self.assertEqual(archive.status, ARCHIVE_STATUS_READY)
        self.assertIsNotNone(result.rework_work_order_id)
        self.assertEqual(return_lot.status, "CLOSED")
        self.assertEqual(return_lot.quality_status, "REWORK")
        self.assertEqual(return_lot.remaining_qty, Decimal("0.000000"))
        self.assertEqual(return_lot.remaining_weight_kg, Decimal("0.000000"))
        self.assertEqual(balance.qty_on_hand, Decimal("0.000000"))
        self.assertEqual(balance.weight_on_hand_kg, Decimal("0.000000"))
        self.assertEqual(txn.qty_change, Decimal("-3.000000"))
        self.assertEqual(txn.weight_change_kg, Decimal("-7.500000"))
        self.assertEqual(txn.source_doc_id, disposition.id)
        self.assertEqual(txn.source_line_id, disposition.id)
        self.assertEqual(disposition.extra_cost_amount, Decimal("18.00"))

    def test_return_rework_outbound_creates_miniapp_visible_rework_work_order(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_order, create_return_rework_outbound
        from app.models.operations import ReturnDisposition, WorkOrder
        from app.schemas.operations import ReturnDispositionCreate, ReturnItemCreate, ReturnOrderCreate

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="客户退回，可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=10,
                        return_weight_kg=Decimal("25"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )

        result = create_return_rework_outbound(
            ReturnDispositionCreate(return_item_id=1, disposition_type="REWORK", extra_cost_amount=20, remark="返工出库"),
            context=Context(),
            db=self.db,
        )

        disposition = self.db.get(ReturnDisposition, result.return_disposition_id)
        work_order = self.db.get(WorkOrder, disposition.rework_work_order_id)

        self.assertIsNotNone(work_order)
        self.assertEqual(work_order.work_order_type, "REWORK")
        self.assertEqual(work_order.product_item_id, seeded["product_item_id"])
        self.assertEqual(work_order.planned_qty, Decimal("10.000000"))
        self.assertEqual(work_order.released_qty, Decimal("10.000000"))
        self.assertRegex(work_order.work_order_no, r"^20\d{2}\d{4}-冲压支架$")
        self.assertEqual(work_order.status, "RELEASED")

    def test_finished_rework_inbound_uses_rework_work_order_good_qty(self) -> None:
        from decimal import Decimal

        from fastapi import HTTPException

        from app.api.routes.production import create_completion_receipt, create_operation_report
        from app.api.routes.returns import create_return_order, create_return_rework_outbound
        from app.models.operations import InventoryTxn, StockLot, WorkOrder, WorkOrderOperation
        from app.schemas.operations import (
            CompletionReceiptCreate,
            CompletionReceiptItemCreate,
            OperationReportCreate,
            ReturnDispositionCreate,
            ReturnItemCreate,
            ReturnOrderCreate,
        )

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="客户退回，可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=10,
                        return_weight_kg=Decimal("25"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )
        disposition = create_return_rework_outbound(
            ReturnDispositionCreate(return_item_id=1, disposition_type="REWORK", extra_cost_amount=20, remark="返工出库"),
            context=Context(),
            db=self.db,
        )
        work_order = self.db.get(WorkOrder, disposition.rework_work_order_id)
        final_operation = self.db.scalars(
            select(WorkOrderOperation)
            .where(WorkOrderOperation.work_order_id == work_order.id)
            .order_by(WorkOrderOperation.seq_no.desc())
        ).first()

        create_operation_report(
            OperationReportCreate(
                work_order_id=work_order.id,
                work_order_operation_id=final_operation.id,
                employee_id=1,
                item_id=seeded["product_item_id"],
                start_time=datetime.now(UTC),
                end_time=datetime.now(UTC) + timedelta(minutes=1),
                report_qty=10,
                good_qty=8,
                scrap_qty=2,
            ),
            db=self.db,
        )

        receipt = create_completion_receipt(
            CompletionReceiptCreate(
                work_order_id=work_order.id,
                warehouse_id=seeded["finished_warehouse_id"],
                settle_work_order=False,
                remark="返工良品回库",
                items=[
                    CompletionReceiptItemCreate(
                        product_item_id=seeded["product_item_id"],
                        receipt_qty=8,
                        receipt_weight_kg=0,
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )

        lot = self.db.scalar(select(StockLot).where(StockLot.source_doc_id == receipt.completion_receipt_id).order_by(StockLot.id.desc()))
        txn = self.db.scalar(select(InventoryTxn).where(InventoryTxn.lot_id == lot.id))

        self.assertEqual(receipt.total_receipt_qty, 8)
        self.assertEqual(lot.inbound_qty, Decimal("8.000000"))
        self.assertEqual(lot.source_doc_type, "REWORK_RECEIPT")
        self.assertIn("退货返工", lot.source_material_summary)
        self.assertEqual(txn.txn_type, "REWORK_FINISHED_IN")
        self.assertEqual(txn.qty_change, Decimal("8.000000"))

        with self.assertRaises(HTTPException) as ctx:
            create_completion_receipt(
                CompletionReceiptCreate(
                    work_order_id=work_order.id,
                    warehouse_id=seeded["finished_warehouse_id"],
                    items=[CompletionReceiptItemCreate(product_item_id=seeded["product_item_id"], receipt_qty=1)],
                ),
                context=Context(),
                db=self.db,
            )
        self.assertEqual(ctx.exception.status_code, 400)
        self.assertIn("返工工单当前没有可返工入库数量", ctx.exception.detail)

    def test_rework_scrap_inbound_plus_finished_inbound_can_settle_rework_order(self) -> None:
        from decimal import Decimal

        from app.api.routes.inventory import create_warehouse_inbound
        from app.api.routes.production import create_completion_receipt, create_operation_report
        from app.api.routes.returns import create_return_order, create_return_rework_outbound
        from app.models.master_data import StockBalance
        from app.models.operations import InventoryTxn, StockLot, WorkOrder, WorkOrderOperation
        from app.schemas.operations import (
            CompletionReceiptCreate,
            CompletionReceiptItemCreate,
            OperationReportCreate,
            ReturnDispositionCreate,
            ReturnItemCreate,
            ReturnOrderCreate,
            WarehouseInboundCreate,
        )

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="客户退回，可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=10,
                        return_weight_kg=Decimal("25"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )
        disposition = create_return_rework_outbound(
            ReturnDispositionCreate(return_item_id=1, disposition_type="REWORK", extra_cost_amount=20, remark="返工出库"),
            context=Context(),
            db=self.db,
        )
        work_order = self.db.get(WorkOrder, disposition.rework_work_order_id)
        final_operation = self.db.scalars(
            select(WorkOrderOperation)
            .where(WorkOrderOperation.work_order_id == work_order.id)
            .order_by(WorkOrderOperation.seq_no.desc())
        ).first()
        create_operation_report(
            OperationReportCreate(
                work_order_id=work_order.id,
                work_order_operation_id=final_operation.id,
                employee_id=1,
                item_id=seeded["product_item_id"],
                start_time=datetime.now(UTC),
                end_time=datetime.now(UTC) + timedelta(minutes=1),
                report_qty=10,
                good_qty=8,
                scrap_qty=2,
            ),
            db=self.db,
        )
        create_completion_receipt(
            CompletionReceiptCreate(
                work_order_id=work_order.id,
                warehouse_id=seeded["finished_warehouse_id"],
                items=[CompletionReceiptItemCreate(product_item_id=seeded["product_item_id"], receipt_qty=8)],
            ),
            context=Context(),
            db=self.db,
        )
        create_warehouse_inbound(
            WarehouseInboundCreate(
                biz_type="REWORK_SCRAP_IN",
                item_id=seeded["product_item_id"],
                warehouse_id=seeded["scrap_warehouse_id"],
                source_doc_type="WORK_ORDER",
                source_doc_id=work_order.id,
                inbound_qty=2,
                inbound_weight_kg=5,
                settle_work_order=True,
                settle_remark="返工良品和返工废料均已处理",
            ),
            context=Context(),
            db=self.db,
        )

        self.db.refresh(work_order)
        scrap_balance = self.db.scalar(select(StockBalance).where(StockBalance.warehouse_id == seeded["scrap_warehouse_id"]))
        scrap_lot = self.db.scalar(select(StockLot).where(StockLot.lot_role == "REWORK_SCRAP"))
        scrap_txn = self.db.scalar(select(InventoryTxn).where(InventoryTxn.txn_type == "REWORK_SCRAP_IN"))

        self.assertEqual(scrap_lot.inbound_qty, Decimal("2.000000"))
        self.assertEqual(scrap_txn.qty_change, Decimal("0.000000"))
        self.assertEqual(scrap_txn.weight_change_kg, Decimal("5.000000"))
        self.assertEqual(scrap_txn.amount, Decimal("0.00"))
        self.assertEqual(scrap_balance.qty_on_hand, Decimal("0.000000"))
        self.assertEqual(scrap_balance.weight_on_hand_kg, Decimal("5.000000"))
        self.assertEqual(work_order.status, "SETTLED")
        self.assertIn("返工工单已结单", work_order.remark)

    def test_miniapp_jh_short_code_binds_to_rework_work_order(self) -> None:
        from decimal import Decimal

        from app.api.routes.production import _new_miniapp_resolve_cache, _sync_miniapp_item_to_erp
        from app.api.routes.returns import create_return_order, create_return_rework_outbound
        from app.models.operations import OperationReport, WorkOrder
        from app.schemas.operations import ReturnDispositionCreate, ReturnItemCreate, ReturnOrderCreate

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="客户退回，可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=10,
                        return_weight_kg=Decimal("25"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )
        disposition = create_return_rework_outbound(
            ReturnDispositionCreate(return_item_id=1, disposition_type="REWORK", remark="返工出库"),
            context=Context(),
            db=self.db,
        )
        work_order = self.db.get(WorkOrder, disposition.rework_work_order_id)
        short_code = f"JH{int(work_order.work_order_no[4:8]):04d}"
        row = {
            "source_report_id": 9001,
            "source_item_id": 900101,
            "project_no": "产品需规00001",
            "product_name": "冲压支架",
            "process_name": "返工",
            "raw_material_batch_no": short_code,
            "employee_phone": "13800000001",
            "employee_name": "返工员工",
            "attendance_point_name": "",
            "good_qty": 8,
            "defect_qty": 0,
            "miniapp_scrap_qty": 2,
            "standard_beat": 10,
            "started_at": datetime.now(UTC),
            "end_time": datetime.now(UTC) + timedelta(minutes=1),
            "status": "approved",
        }

        report = _sync_miniapp_item_to_erp(
            self.db,
            int(row["source_item_id"]),
            required=True,
            row=row,
            cache=_new_miniapp_resolve_cache(),
        )

        saved = self.db.get(OperationReport, report.id)
        self.assertEqual(saved.work_order_id, work_order.id)
        self.assertEqual(saved.good_qty, Decimal("8.000000"))
        self.assertEqual(saved.scrap_qty, Decimal("2.000000"))

    def test_return_disposition_query_exposes_rework_closed_loop_summary(self) -> None:
        from decimal import Decimal

        from app.api.routes.inventory import create_warehouse_inbound
        from app.api.routes.production import create_completion_receipt, create_operation_report
        from app.api.routes.returns import create_return_order, create_return_rework_outbound
        from app.models.operations import WorkOrder, WorkOrderOperation
        from app.schemas.operations import (
            CompletionReceiptCreate,
            CompletionReceiptItemCreate,
            OperationReportCreate,
            ReturnDispositionCreate,
            ReturnItemCreate,
            ReturnOrderCreate,
            WarehouseInboundCreate,
        )
        from app.services.operations import get_return_dispositions_query

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="客户退回，可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=10,
                        return_weight_kg=Decimal("25"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )
        disposition = create_return_rework_outbound(
            ReturnDispositionCreate(return_item_id=1, disposition_type="REWORK", remark="返工出库"),
            context=Context(),
            db=self.db,
        )
        work_order = self.db.get(WorkOrder, disposition.rework_work_order_id)
        final_operation = self.db.scalars(
            select(WorkOrderOperation)
            .where(WorkOrderOperation.work_order_id == work_order.id)
            .order_by(WorkOrderOperation.seq_no.desc())
        ).first()
        create_operation_report(
            OperationReportCreate(
                work_order_id=work_order.id,
                work_order_operation_id=final_operation.id,
                employee_id=1,
                item_id=seeded["product_item_id"],
                start_time=datetime.now(UTC),
                end_time=datetime.now(UTC) + timedelta(minutes=1),
                report_qty=10,
                good_qty=8,
                scrap_qty=2,
            ),
            db=self.db,
        )
        create_completion_receipt(
            CompletionReceiptCreate(
                work_order_id=work_order.id,
                warehouse_id=seeded["finished_warehouse_id"],
                items=[CompletionReceiptItemCreate(product_item_id=seeded["product_item_id"], receipt_qty=8)],
            ),
            context=Context(),
            db=self.db,
        )
        create_warehouse_inbound(
            WarehouseInboundCreate(
                biz_type="REWORK_SCRAP_IN",
                item_id=seeded["product_item_id"],
                warehouse_id=seeded["scrap_warehouse_id"],
                source_doc_type="WORK_ORDER",
                source_doc_id=work_order.id,
                inbound_qty=2,
                inbound_weight_kg=5,
                settle_work_order=True,
            ),
            context=Context(),
            db=self.db,
        )

        row = self.db.execute(get_return_dispositions_query(limit=1)).mappings().first()

        self.assertEqual(row["return_qty"], Decimal("10.000000"))
        self.assertEqual(row["rework_report_good_qty"], Decimal("8.000000"))
        self.assertEqual(row["rework_report_scrap_qty"], Decimal("2.000000"))
        self.assertEqual(row["rework_finished_inbound_qty"], Decimal("8.000000"))
        self.assertEqual(row["rework_scrap_inbound_qty"], Decimal("2.000000"))
        self.assertEqual(row["remaining_rework_finished_inbound_qty"], Decimal("0.000000"))
        self.assertEqual(row["remaining_rework_scrap_inbound_qty"], Decimal("0.000000"))
        self.assertEqual(row["rework_close_ready"], True)
        self.assertEqual(row["rework_close_status"], "已结单")

    def test_return_rework_outbound_rejects_non_rework_type(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_order, create_return_rework_outbound
        from app.schemas.operations import ReturnDispositionCreate, ReturnItemCreate, ReturnOrderCreate
        from fastapi import HTTPException

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=3,
                        return_weight_kg=Decimal("7.5"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )

        with self.assertRaises(HTTPException) as exc:
            create_return_rework_outbound(
                ReturnDispositionCreate(return_item_id=1, disposition_type="SCRAP", scrap_weight_kg=Decimal("7.5")),
                context=Context(),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 400)
        self.assertIn("退货库出库只支持返工出库", exc.exception.detail)

    def test_return_rework_outbound_rejects_lot_with_inconsistent_balance(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_order, create_return_rework_outbound
        from app.models.operations import StockLot
        from app.schemas.operations import ReturnDispositionCreate, ReturnItemCreate, ReturnOrderCreate
        from fastapi import HTTPException

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=3,
                        return_weight_kg=Decimal("7.5"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )
        return_lot = self.db.scalar(select(StockLot).where(StockLot.lot_role == "RETURNED_FINISHED"))
        return_lot.remaining_qty = Decimal("2")
        self.db.add(return_lot)
        self.db.commit()

        with self.assertRaises(HTTPException) as exc:
            create_return_rework_outbound(
                ReturnDispositionCreate(return_item_id=1, disposition_type="REWORK"),
                context=Context(),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 400)
        self.assertIn("退货批次数量不足", exc.exception.detail)

    def test_return_rework_outbound_rejects_aggregate_stock_balance_shortage(self) -> None:
        from decimal import Decimal

        from app.api.routes.returns import create_return_order, create_return_rework_outbound
        from app.models.master_data import StockBalance
        from app.schemas.operations import ReturnDispositionCreate, ReturnItemCreate, ReturnOrderCreate
        from fastapi import HTTPException

        class Context:
            user = type("User", (), {"id": 1})()

        seeded = self.seed_delivery_trace()
        create_return_order(
            ReturnOrderCreate(
                customer_id=seeded["customer_id"],
                delivery_id=seeded["delivery_id"],
                warehouse_id=seeded["return_warehouse_id"],
                return_reason="可返工",
                items=[
                    ReturnItemCreate(
                        delivery_item_id=seeded["delivery_item_id"],
                        product_item_id=seeded["product_item_id"],
                        return_qty=3,
                        return_weight_kg=Decimal("7.5"),
                    )
                ],
            ),
            context=Context(),
            db=self.db,
        )
        balance = self.db.scalar(select(StockBalance).where(StockBalance.warehouse_id == seeded["return_warehouse_id"]))
        balance.qty_on_hand = Decimal("2")
        self.db.add(balance)
        self.db.commit()

        with self.assertRaises(HTTPException) as exc:
            create_return_rework_outbound(
                ReturnDispositionCreate(return_item_id=1, disposition_type="REWORK"),
                context=Context(),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 400)
        self.assertIn("库存余额不足", exc.exception.detail)
