from datetime import datetime
from decimal import Decimal

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import func, select
from sqlalchemy.orm import Session, aliased

from app.db.session import get_db
from app.models.master_data import Item, Warehouse
from app.models.operations import Delivery, DeliveryItem, ReturnDisposition, ReturnItem, ReturnOrder, StockLot
from app.models.sales import Customer, SalesOrder, SalesOrderItem
from app.schemas.operations import (
    ReturnDispositionCreate,
    ReturnDispositionRead,
    ReturnItemRead,
    ReturnOrderCreate,
    ReturnOrderRead,
    ReturnScrapInboundCreate,
)
from app.services.auth import AuthContext, require_authenticated_user
from app.services.document_archives import DOCUMENT_TYPE_WAREHOUSE_OPERATION, generate_document_archive
from app.services.operations import (
    create_inventory_txn,
    create_rework_work_order,
    get_location_or_default,
    get_return_dispositions_query,
    get_return_items_query,
    get_return_orders_query,
    sync_return_order_status,
    to_decimal,
    upsert_stock_balance,
)
from app.services.system_permissions import ensure_employee_has_permission
router = APIRouter(dependencies=[Depends(require_authenticated_user)])


def _generate_return_archive_fields(
    db: Session,
    *,
    business_id: int | None,
    created_by: int | None,
) -> dict[str, object]:
    if not business_id:
        return {
            "archive_status": None,
            "archive_business_id": None,
            "archive_document_type": None,
            "archive_error_message": None,
        }
    try:
        archive_result = generate_document_archive(
            db,
            DOCUMENT_TYPE_WAREHOUSE_OPERATION,
            int(business_id),
            created_by=created_by,
        )
        return {
            "archive_status": archive_result.archive_status,
            "archive_business_id": int(business_id),
            "archive_document_type": DOCUMENT_TYPE_WAREHOUSE_OPERATION,
            "archive_error_message": archive_result.archive_error_message,
        }
    except Exception:
        return {
            "archive_status": "归档失败",
            "archive_business_id": int(business_id),
            "archive_document_type": DOCUMENT_TYPE_WAREHOUSE_OPERATION,
            "archive_error_message": "归档失败：PDF归档服务异常，请稍后重新生成",
        }


def _apply_archive_fields_to_model(model, archive_fields: dict[str, object]):
    model.archive_status = archive_fields["archive_status"]
    model.archive_business_id = archive_fields["archive_business_id"]
    model.archive_document_type = archive_fields["archive_document_type"]
    model.archive_error_message = archive_fields["archive_error_message"]
    return model


def get_return_warehouse(db: Session, warehouse_id: int | None) -> Warehouse:
    if warehouse_id:
        warehouse = db.get(Warehouse, warehouse_id)
    else:
        warehouse = db.scalar(select(Warehouse).where(Warehouse.warehouse_type == "RETURN", Warehouse.status == "ACTIVE"))
    if not warehouse:
        raise HTTPException(status_code=404, detail="退货仓不存在")
    if str(warehouse.warehouse_type or "").upper() != "RETURN":
        raise HTTPException(status_code=400, detail="退货入库只能进入退货库")
    if str(warehouse.status or "").upper() != "ACTIVE":
        raise HTTPException(status_code=400, detail="退货仓未启用，不能办理退货入库")
    return warehouse


def _serial_code_part(value: str | None, fallback: str, max_length: int = 14) -> str:
    token = "".join(ch for ch in str(value or "").upper() if ch.isalnum())
    return (token or fallback)[-max_length:]


def build_return_no(db: Session, customer: Customer) -> str:
    date_part = datetime.now().strftime("%Y%m%d")
    customer_part = _serial_code_part(customer.short_name or customer.customer_code, f"CUS{customer.id:04d}")
    prefix = f"RTN-NB-{date_part}-{customer_part}"
    count = db.scalar(select(func.count(ReturnOrder.id)).where(ReturnOrder.return_no.like(f"{prefix}-%"))) or 0
    return f"{prefix}-{count + 1:03d}"


def build_return_disposition_no(db: Session, return_order: ReturnOrder, disposition_type: str) -> str:
    date_part = datetime.now().strftime("%Y%m%d")
    type_part = "RWK" if disposition_type == "REWORK" else "SCRAP"
    return_part = _serial_code_part(return_order.return_no, f"RTN{return_order.id:04d}", 18)
    prefix = f"RTD-NB-{type_part}-{date_part}-{return_part}"
    count = db.scalar(select(func.count(ReturnDisposition.id)).where(ReturnDisposition.disposition_no.like(f"{prefix}-%"))) or 0
    return f"{prefix}-{count + 1:03d}"


def build_return_lot_no(db: Session, return_order: ReturnOrder, product: Item, line_no: int) -> str:
    date_part = datetime.now().strftime("%Y%m%d")
    return_part = _serial_code_part(return_order.return_no, f"RTN{return_order.id:04d}", 12)
    product_part = _serial_code_part(product.item_code or product.item_name, f"FG{product.id:04d}", 10)
    base_lot_no = f"RTL-NB-{date_part}-{return_part}-{product_part}-L{line_no:02d}"
    candidate = base_lot_no
    suffix = 1
    while db.scalar(select(func.count(StockLot.id)).where(StockLot.lot_no == candidate)):
        suffix += 1
        candidate = f"{base_lot_no}-{suffix:02d}"
    return candidate


def get_delivery_item_trace(db: Session, delivery_item_id: int) -> dict:
    source_lot = aliased(StockLot)
    source_material_lot = aliased(StockLot)
    source_material_item = aliased(Item)
    row = db.execute(
        select(
            DeliveryItem.id.label("delivery_item_id"),
            Delivery.id.label("delivery_id"),
            Delivery.delivery_no.label("delivery_no"),
            Delivery.status.label("delivery_status"),
            Delivery.customer_id.label("customer_id"),
            Delivery.sales_order_id.label("sales_order_id"),
            DeliveryItem.sales_order_item_id.label("sales_order_item_id"),
            DeliveryItem.product_item_id.label("product_item_id"),
            DeliveryItem.status.label("delivery_item_status"),
            source_lot.id.label("source_lot_id"),
            DeliveryItem.delivery_qty.label("delivery_qty"),
            DeliveryItem.delivery_weight_kg.label("delivery_weight_kg"),
            source_lot.lot_no.label("source_lot_no"),
            source_lot.source_material_lot_id.label("source_material_lot_id"),
            source_material_lot.lot_no.label("source_material_lot_no"),
            source_material_item.item_code.label("source_material_code"),
            source_material_item.item_name.label("source_material_name"),
            source_lot.source_material_sub_batch_no.label("source_material_sub_batch_no"),
            source_lot.source_material_summary.label("source_material_summary"),
        )
        .join(Delivery, Delivery.id == DeliveryItem.delivery_id)
        .join(source_lot, source_lot.id == DeliveryItem.lot_id)
        .outerjoin(source_material_lot, source_material_lot.id == source_lot.source_material_lot_id)
        .outerjoin(source_material_item, source_material_item.id == source_material_lot.item_id)
        .where(DeliveryItem.id == delivery_item_id)
    ).mappings().first()
    if not row:
        raise HTTPException(status_code=404, detail="发货批次不存在")
    return dict(row)


def _returned_totals_for_delivery_item(db: Session, delivery_item_id: int) -> tuple[Decimal, Decimal]:
    row = db.execute(
        select(
            func.coalesce(func.sum(ReturnItem.return_qty), 0).label("returned_qty"),
            func.coalesce(func.sum(ReturnItem.return_weight_kg), 0).label("returned_weight"),
        ).where(ReturnItem.delivery_item_id == delivery_item_id)
    ).mappings().first()
    return to_decimal(row["returned_qty"]), to_decimal(row["returned_weight"])


def _lock_delivery_items_for_return(db: Session, delivery_item_ids: set[int]) -> None:
    ids = sorted(int(item_id) for item_id in delivery_item_ids if item_id)
    if not ids:
        return
    db.execute(select(DeliveryItem.id).where(DeliveryItem.id.in_(ids)).with_for_update()).all()


def _ensure_return_amount_available(
    db: Session,
    *,
    trace: dict,
    requested_qty: Decimal,
    requested_weight: Decimal,
    staged_qty: Decimal = Decimal("0"),
    staged_weight: Decimal = Decimal("0"),
) -> None:
    returned_qty, returned_weight = _returned_totals_for_delivery_item(db, int(trace["delivery_item_id"]))
    delivery_qty = to_decimal(trace["delivery_qty"])
    delivery_weight = to_decimal(trace["delivery_weight_kg"])
    if delivery_qty > 0 and returned_qty + staged_qty + requested_qty > delivery_qty:
        raise HTTPException(status_code=400, detail="退货数量超过发货批次可退数量")
    if delivery_weight > 0 and returned_weight + staged_weight + requested_weight > delivery_weight:
        raise HTTPException(status_code=400, detail="退货重量超过发货批次可退重量")


def _validate_return_delivery_trace(
    item_payload,
    trace: dict,
    *,
    customer_id: int,
    delivery_id: int | None,
    sales_order_id: int | None,
) -> None:
    if str(trace.get("delivery_status") or "").upper() != "POSTED":
        raise HTTPException(status_code=400, detail="发货单未过账，不能办理退货")
    if str(trace.get("delivery_item_status") or "").upper() != "POSTED":
        raise HTTPException(status_code=400, detail="发货批次未过账，不能办理退货")
    if trace["customer_id"] != customer_id:
        raise HTTPException(status_code=400, detail="发货批次客户与退货客户不一致")
    if delivery_id and trace["delivery_id"] != delivery_id:
        raise HTTPException(status_code=400, detail="发货批次不属于所选发货单")
    if sales_order_id and trace["sales_order_id"] and trace["sales_order_id"] != sales_order_id:
        raise HTTPException(status_code=400, detail="发货批次不属于所选订单")
    if item_payload.product_item_id != trace["product_item_id"]:
        raise HTTPException(status_code=400, detail="退货产品与发货批次不一致")
    if item_payload.source_lot_id and item_payload.source_lot_id != trace["source_lot_id"]:
        raise HTTPException(status_code=400, detail="退货来源批次与发货批次不一致")
    if (
        getattr(item_payload, "sales_order_item_id", None)
        and trace["sales_order_item_id"]
        and item_payload.sales_order_item_id != trace["sales_order_item_id"]
    ):
        raise HTTPException(status_code=400, detail="退货订单明细与发货批次不一致")


@router.get("/orders", response_model=list[ReturnOrderRead])
def list_return_orders(
    limit: int = Query(default=100, ge=1, le=500),
    db: Session = Depends(get_db),
) -> list[ReturnOrderRead]:
    rows = db.execute(get_return_orders_query(limit=limit)).mappings().all()
    return [ReturnOrderRead.model_validate(dict(row)) for row in rows]


@router.get("/items", response_model=list[ReturnItemRead])
def list_return_items(
    return_order_id: int | None = Query(default=None),
    limit: int = Query(default=200, ge=1, le=500),
    db: Session = Depends(get_db),
) -> list[ReturnItemRead]:
    rows = db.execute(get_return_items_query(limit=limit, return_order_id=return_order_id)).mappings().all()
    return [ReturnItemRead.model_validate(dict(row)) for row in rows]


@router.post("/orders", response_model=ReturnOrderRead)
def create_return_order(
    payload: ReturnOrderCreate,
    context: AuthContext = Depends(require_authenticated_user),
    db: Session = Depends(get_db),
) -> ReturnOrderRead:
    customer = db.get(Customer, payload.customer_id)
    if not customer:
        raise HTTPException(status_code=404, detail="客户不存在")
    if payload.sales_order_id and not db.get(SalesOrder, payload.sales_order_id):
        raise HTTPException(status_code=404, detail="订单不存在")
    if payload.delivery_id and not db.get(Delivery, payload.delivery_id):
        raise HTTPException(status_code=404, detail="发货单不存在")
    if not payload.items:
        raise HTTPException(status_code=400, detail="退货单至少需要一条明细")

    warehouse = get_return_warehouse(db, payload.warehouse_id)
    prepared_items = []
    staged_by_delivery_item: dict[int, tuple[Decimal, Decimal]] = {}
    order_delivery_id = payload.delivery_id
    order_sales_order_id = payload.sales_order_id
    for item_payload in payload.items:
        if not item_payload.delivery_item_id:
            raise HTTPException(status_code=400, detail="退货入库必须选择发货批次")
        trace = get_delivery_item_trace(db, item_payload.delivery_item_id)
        if order_delivery_id is None:
            order_delivery_id = trace["delivery_id"]
        if order_sales_order_id is None:
            order_sales_order_id = trace["sales_order_id"]
        _validate_return_delivery_trace(
            item_payload,
            trace,
            customer_id=payload.customer_id,
            delivery_id=order_delivery_id,
            sales_order_id=order_sales_order_id,
        )
        product = db.get(Item, item_payload.product_item_id)
        if not product:
            raise HTTPException(status_code=404, detail=f"产品不存在: {item_payload.product_item_id}")
        if item_payload.sales_order_item_id:
            order_item = db.get(SalesOrderItem, item_payload.sales_order_item_id)
            if not order_item:
                raise HTTPException(status_code=404, detail=f"订单明细不存在: {item_payload.sales_order_item_id}")
        return_qty = to_decimal(item_payload.return_qty)
        if return_qty <= 0:
            raise HTTPException(status_code=400, detail="退货数量必须大于0")
        return_weight = to_decimal(item_payload.return_weight_kg)
        if return_weight <= 0:
            return_weight = return_qty * to_decimal(product.unit_weight_kg)
        if return_weight <= 0:
            raise HTTPException(status_code=400, detail="退货重量必须大于0")
        staged_qty, staged_weight = staged_by_delivery_item.get(
            int(item_payload.delivery_item_id),
            (Decimal("0"), Decimal("0")),
        )
        _ensure_return_amount_available(
            db,
            trace=trace,
            requested_qty=return_qty,
            requested_weight=return_weight,
            staged_qty=staged_qty,
            staged_weight=staged_weight,
        )
        staged_by_delivery_item[int(item_payload.delivery_item_id)] = (
            staged_qty + return_qty,
            staged_weight + return_weight,
        )
        prepared_items.append((item_payload, trace, product, return_qty, return_weight))

    ensure_employee_has_permission(
        db,
        payload.handler_employee_id,
        "MENU_INVENTORY_LEDGER",
        "经办人",
        required=False,
    )
    _lock_delivery_items_for_return(db, {int(item.delivery_item_id) for item, *_ in prepared_items})
    staged_by_delivery_item = {}
    for item_payload, trace, _product, return_qty, return_weight in prepared_items:
        staged_qty, staged_weight = staged_by_delivery_item.get(
            int(item_payload.delivery_item_id),
            (Decimal("0"), Decimal("0")),
        )
        _ensure_return_amount_available(
            db,
            trace=trace,
            requested_qty=return_qty,
            requested_weight=return_weight,
            staged_qty=staged_qty,
            staged_weight=staged_weight,
        )
        staged_by_delivery_item[int(item_payload.delivery_item_id)] = (
            staged_qty + return_qty,
            staged_weight + return_weight,
        )

    order = ReturnOrder(
        return_no=build_return_no(db, customer),
        customer_id=payload.customer_id,
        sales_order_id=order_sales_order_id,
        delivery_id=order_delivery_id,
        return_date=payload.return_date or datetime.now(),
        return_reason=payload.return_reason,
        handler_employee_id=payload.handler_employee_id,
        status="CREATED",
        remark=payload.remark,
    )
    db.add(order)
    db.flush()

    location = get_location_or_default(db, warehouse.id, None)
    first_inventory_txn_id: int | None = None
    for index, (item_payload, trace, product, return_qty, return_weight) in enumerate(prepared_items, start=1):
        return_item = ReturnItem(
            return_order_id=order.id,
            line_no=index,
            sales_order_item_id=item_payload.sales_order_item_id or trace["sales_order_item_id"],
            delivery_item_id=item_payload.delivery_item_id,
            product_item_id=item_payload.product_item_id,
            source_lot_id=trace["source_lot_id"],
            return_qty=return_qty,
            return_weight_kg=return_weight,
            disposition_status="PENDING",
            responsibility_type=item_payload.responsibility_type,
            remark=item_payload.remark,
        )
        db.add(return_item)
        db.flush()

        lot = StockLot(
            lot_no=build_return_lot_no(db, order, product, index),
            parent_lot_id=trace["source_lot_id"],
            lot_role="RETURNED_FINISHED",
            material_sub_batch_no=None,
            item_id=item_payload.product_item_id,
            warehouse_id=warehouse.id,
            location_id=location.id if location else None,
            source_doc_type="RETURN_ORDER",
            source_doc_id=order.id,
            source_line_id=return_item.id,
            source_material_lot_id=trace["source_material_lot_id"],
            source_material_sub_batch_no=trace["source_material_sub_batch_no"],
            source_material_summary=trace["source_material_summary"],
            inbound_qty=return_qty,
            inbound_weight_kg=return_weight,
            remaining_qty=return_qty,
            remaining_weight_kg=return_weight,
            locked_qty=return_qty,
            locked_weight_kg=return_weight,
            unit_cost=to_decimal(0, "0.0001"),
            production_date=None,
            expire_date=None,
            quality_status="PENDING",
            status="LOCKED",
            remark="客户退货待处置",
        )
        db.add(lot)
        db.flush()

        upsert_stock_balance(
            db,
            item_id=item_payload.product_item_id,
            warehouse_id=warehouse.id,
            location_id=location.id if location else None,
            qty_delta=return_qty,
            weight_delta=return_weight,
            available_qty_delta=to_decimal(0),
            available_weight_delta=to_decimal(0),
            unit_cost=to_decimal(0, "0.0001"),
        )
        txn = create_inventory_txn(
            db,
            txn_type="RETURN_IN",
            item_id=item_payload.product_item_id,
            warehouse_id=warehouse.id,
            location_id=location.id if location else None,
            lot_id=lot.id,
            qty_change=return_qty,
            weight_change=return_weight,
            unit_cost=to_decimal(0, "0.0001"),
            source_doc_type="RETURN_ORDER",
            source_doc_id=order.id,
            source_line_id=return_item.id,
            biz_time=order.return_date,
            operator_user_id=context.user.id,
            remark="客户退货入库",
            amount_basis="QTY",
        )
        if first_inventory_txn_id is None:
            first_inventory_txn_id = int(txn.id)

    db.commit()
    archive_fields = _generate_return_archive_fields(db, business_id=first_inventory_txn_id, created_by=context.user.id)

    row = db.execute(get_return_orders_query(limit=1).where(ReturnOrder.id == order.id)).mappings().first()
    if not row:
        raise HTTPException(status_code=500, detail="退货单创建后读取失败")
    return _apply_archive_fields_to_model(ReturnOrderRead.model_validate(dict(row)), archive_fields)


@router.post("/scrap-inbound", response_model=ReturnOrderRead)
def create_return_scrap_inbound(
    payload: ReturnScrapInboundCreate,
    context: AuthContext = Depends(require_authenticated_user),
    db: Session = Depends(get_db),
) -> ReturnOrderRead:
    customer = db.get(Customer, payload.customer_id)
    if not customer:
        raise HTTPException(status_code=404, detail="客户不存在")
    delivery = db.get(Delivery, payload.delivery_id)
    if not delivery:
        raise HTTPException(status_code=404, detail="发货单不存在")
    warehouse = db.get(Warehouse, payload.warehouse_id)
    if not warehouse:
        raise HTTPException(status_code=404, detail="废料仓不存在")
    if str(warehouse.warehouse_type or "").upper() != "SCRAP":
        raise HTTPException(status_code=400, detail="退货报废入库只能进入废料库")
    if str(warehouse.status or "").upper() != "ACTIVE":
        raise HTTPException(status_code=400, detail="废料仓未启用，不能办理退货报废入库")
    if not payload.items:
        raise HTTPException(status_code=400, detail="退货报废入库至少需要一条明细")

    prepared_items = []
    staged_by_delivery_item: dict[int, tuple[Decimal, Decimal]] = {}
    for item_payload in payload.items:
        trace = get_delivery_item_trace(db, item_payload.delivery_item_id)
        _validate_return_delivery_trace(
            item_payload,
            trace,
            customer_id=payload.customer_id,
            delivery_id=payload.delivery_id,
            sales_order_id=delivery.sales_order_id,
        )
        product = db.get(Item, item_payload.product_item_id)
        if not product:
            raise HTTPException(status_code=404, detail=f"产品不存在: {item_payload.product_item_id}")
        scrap_weight = to_decimal(item_payload.scrap_weight_kg)
        if scrap_weight <= 0:
            raise HTTPException(status_code=400, detail="退货报废入库重量必须大于0")
        scrap_qty = to_decimal(item_payload.scrap_qty)
        unit_weight = to_decimal(product.unit_weight_kg)
        if scrap_qty <= 0 and unit_weight > 0:
            scrap_qty = scrap_weight / unit_weight
        if scrap_qty <= 0:
            raise HTTPException(status_code=400, detail="退货报废数量必须大于0")
        staged_qty, staged_weight = staged_by_delivery_item.get(
            int(item_payload.delivery_item_id),
            (Decimal("0"), Decimal("0")),
        )
        _ensure_return_amount_available(
            db,
            trace=trace,
            requested_qty=scrap_qty,
            requested_weight=scrap_weight,
            staged_qty=staged_qty,
            staged_weight=staged_weight,
        )
        staged_by_delivery_item[int(item_payload.delivery_item_id)] = (
            staged_qty + scrap_qty,
            staged_weight + scrap_weight,
        )
        unit_cost = (
            to_decimal(item_payload.unit_cost, "0.0001")
            if item_payload.unit_cost is not None
            else to_decimal(product.scrap_sale_price or 0, "0.0001")
        )
        prepared_items.append((item_payload, trace, product, scrap_qty, scrap_weight, unit_cost))

    ensure_employee_has_permission(
        db,
        payload.handler_employee_id,
        "MENU_INVENTORY_LEDGER",
        "经办人",
        required=False,
    )
    _lock_delivery_items_for_return(db, {int(item.delivery_item_id) for item, *_ in prepared_items})
    staged_by_delivery_item = {}
    for item_payload, trace, _product, scrap_qty, scrap_weight, _unit_cost in prepared_items:
        staged_qty, staged_weight = staged_by_delivery_item.get(
            int(item_payload.delivery_item_id),
            (Decimal("0"), Decimal("0")),
        )
        _ensure_return_amount_available(
            db,
            trace=trace,
            requested_qty=scrap_qty,
            requested_weight=scrap_weight,
            staged_qty=staged_qty,
            staged_weight=staged_weight,
        )
        staged_by_delivery_item[int(item_payload.delivery_item_id)] = (
            staged_qty + scrap_qty,
            staged_weight + scrap_weight,
        )

    inbound_time = payload.inbound_date or datetime.now()
    order = ReturnOrder(
        return_no=build_return_no(db, customer),
        customer_id=payload.customer_id,
        sales_order_id=delivery.sales_order_id,
        delivery_id=payload.delivery_id,
        return_date=inbound_time,
        return_reason=payload.return_reason,
        handler_employee_id=payload.handler_employee_id,
        status="CLOSED",
        remark=payload.remark,
    )
    db.add(order)
    db.flush()

    location = get_location_or_default(db, warehouse.id, None)
    first_inventory_txn_id: int | None = None
    for index, (item_payload, trace, product, scrap_qty, scrap_weight, unit_cost) in enumerate(prepared_items, start=1):
        return_item = ReturnItem(
            return_order_id=order.id,
            line_no=index,
            sales_order_item_id=trace["sales_order_item_id"],
            delivery_item_id=item_payload.delivery_item_id,
            product_item_id=item_payload.product_item_id,
            source_lot_id=trace["source_lot_id"],
            return_qty=scrap_qty,
            return_weight_kg=scrap_weight,
            disposition_status="CLOSED",
            responsibility_type=None,
            remark=item_payload.remark,
        )
        db.add(return_item)
        db.flush()

        lot = StockLot(
            lot_no=build_return_lot_no(db, order, product, index).replace("RTL", "RTS", 1),
            parent_lot_id=trace["source_lot_id"],
            lot_role="CUSTOMER_RETURN_SCRAP",
            material_sub_batch_no=None,
            item_id=item_payload.product_item_id,
            warehouse_id=warehouse.id,
            location_id=location.id if location else None,
            source_doc_type="RETURN_ORDER",
            source_doc_id=order.id,
            source_line_id=return_item.id,
            source_material_lot_id=trace["source_material_lot_id"],
            source_material_sub_batch_no=trace["source_material_sub_batch_no"],
            source_material_summary=trace["source_material_summary"],
            inbound_qty=scrap_qty,
            inbound_weight_kg=scrap_weight,
            remaining_qty=scrap_qty,
            remaining_weight_kg=scrap_weight,
            locked_qty=to_decimal(0),
            locked_weight_kg=to_decimal(0),
            unit_cost=unit_cost,
            production_date=None,
            expire_date=None,
            quality_status="SCRAP",
            status="AVAILABLE",
            remark="客户退货直接报废入库",
        )
        db.add(lot)
        db.flush()

        upsert_stock_balance(
            db,
            item_id=item_payload.product_item_id,
            warehouse_id=warehouse.id,
            location_id=location.id if location else None,
            qty_delta=scrap_qty,
            weight_delta=scrap_weight,
            available_qty_delta=scrap_qty,
            available_weight_delta=scrap_weight,
            unit_cost=unit_cost,
        )
        txn = create_inventory_txn(
            db,
            txn_type="RETURN_SCRAP_IN",
            item_id=item_payload.product_item_id,
            warehouse_id=warehouse.id,
            location_id=location.id if location else None,
            lot_id=lot.id,
            qty_change=scrap_qty,
            weight_change=scrap_weight,
            unit_cost=unit_cost,
            source_doc_type="RETURN_ORDER",
            source_doc_id=order.id,
            source_line_id=return_item.id,
            biz_time=inbound_time,
            operator_user_id=context.user.id,
            remark="客户退货报废入库",
            amount_basis="WEIGHT",
        )
        if first_inventory_txn_id is None:
            first_inventory_txn_id = int(txn.id)

        disposition = ReturnDisposition(
            disposition_no=build_return_disposition_no(db, order, "SCRAP"),
            return_item_id=return_item.id,
            disposition_type="SCRAP",
            rework_work_order_id=None,
            scrap_weight_kg=scrap_weight,
            extra_cost_amount=to_decimal(0, "0.01"),
            scrap_sale_amount=to_decimal(scrap_weight * unit_cost, "0.01"),
            closed_at=datetime.now(),
            status="CLOSED",
            remark=item_payload.remark or payload.remark,
        )
        db.add(disposition)

    db.commit()
    archive_fields = _generate_return_archive_fields(db, business_id=first_inventory_txn_id, created_by=context.user.id)

    row = db.execute(get_return_orders_query(limit=1).where(ReturnOrder.id == order.id)).mappings().first()
    if not row:
        raise HTTPException(status_code=500, detail="退货报废入库创建后读取失败")
    return _apply_archive_fields_to_model(ReturnOrderRead.model_validate(dict(row)), archive_fields)


@router.get("/dispositions", response_model=list[ReturnDispositionRead])
def list_return_dispositions(
    limit: int = Query(default=100, ge=1, le=500),
    db: Session = Depends(get_db),
) -> list[ReturnDispositionRead]:
    rows = db.execute(get_return_dispositions_query(limit=limit)).mappings().all()
    return [ReturnDispositionRead.model_validate(dict(row)) for row in rows]


@router.post("/dispositions", response_model=ReturnDispositionRead)
def create_return_disposition(
    payload: ReturnDispositionCreate,
    context: AuthContext = Depends(require_authenticated_user),
    db: Session = Depends(get_db),
) -> ReturnDispositionRead:
    return_item = db.scalar(select(ReturnItem).where(ReturnItem.id == payload.return_item_id).with_for_update())
    if not return_item:
        raise HTTPException(status_code=404, detail="退货明细不存在")
    if return_item.disposition_status == "CLOSED":
        raise HTTPException(status_code=400, detail="该退货明细已完成处置")
    return_order = db.get(ReturnOrder, return_item.return_order_id)
    if not return_order:
        raise HTTPException(status_code=404, detail="退货单不存在")

    lot = db.scalar(
        select(StockLot).where(
            StockLot.source_doc_type == "RETURN_ORDER",
            StockLot.source_doc_id == return_item.return_order_id,
            StockLot.source_line_id == return_item.id,
        ).with_for_update()
    )
    if not lot:
        raise HTTPException(status_code=404, detail="退货批次不存在")

    disposition_type = payload.disposition_type.upper()
    if disposition_type != "REWORK":
        raise HTTPException(status_code=400, detail="退货库处置只支持返工，报废退货请从废料库办理退货废料入库")
    warehouse = db.get(Warehouse, lot.warehouse_id)
    if str(warehouse.warehouse_type if warehouse else "").upper() != "RETURN":
        raise HTTPException(status_code=400, detail="退货批次不在退货库，不能返工出库")
    if lot.lot_role != "RETURNED_FINISHED":
        raise HTTPException(status_code=400, detail="退货批次类型不正确，不能返工出库")
    if lot.status != "LOCKED" or lot.quality_status != "PENDING":
        raise HTTPException(status_code=400, detail="退货批次状态不是待返工锁定状态")
    required_qty = to_decimal(return_item.return_qty)
    required_weight = to_decimal(return_item.return_weight_kg)
    if to_decimal(lot.remaining_qty) < required_qty or to_decimal(lot.locked_qty) < required_qty:
        raise HTTPException(status_code=400, detail="退货批次数量不足，不能返工出库")
    if to_decimal(lot.remaining_weight_kg) < required_weight or to_decimal(lot.locked_weight_kg) < required_weight:
        raise HTTPException(status_code=400, detail="退货批次重量不足，不能返工出库")

    rework_work_order_id = None
    if disposition_type == "REWORK":
        rework_order = create_rework_work_order(
            db,
            product_item_id=return_item.product_item_id,
            planned_qty=to_decimal(return_item.return_qty),
            remark=payload.remark or "由退货处置自动生成返工工单",
        )
        rework_work_order_id = rework_order.id

    lot.remaining_qty = to_decimal(0)
    lot.remaining_weight_kg = to_decimal(0)
    lot.locked_qty = to_decimal(0)
    lot.locked_weight_kg = to_decimal(0)
    lot.status = "CLOSED"
    lot.quality_status = "REWORK"
    db.add(lot)

    disposition = ReturnDisposition(
        disposition_no=build_return_disposition_no(db, return_order, disposition_type),
        return_item_id=payload.return_item_id,
        disposition_type=disposition_type,
        rework_work_order_id=rework_work_order_id,
        scrap_weight_kg=to_decimal(payload.scrap_weight_kg),
        extra_cost_amount=to_decimal(payload.extra_cost_amount, "0.01"),
        scrap_sale_amount=to_decimal(payload.scrap_sale_amount, "0.01"),
        closed_at=datetime.now(),
        status="CLOSED",
        remark=payload.remark,
    )
    db.add(disposition)
    db.flush()

    upsert_stock_balance(
        db,
        item_id=lot.item_id,
        warehouse_id=lot.warehouse_id,
        location_id=lot.location_id,
        qty_delta=-required_qty,
        weight_delta=-required_weight,
        available_qty_delta=to_decimal(0),
        available_weight_delta=to_decimal(0),
        unit_cost=to_decimal(lot.unit_cost, "0.0001"),
    )
    txn = create_inventory_txn(
        db,
        txn_type="RETURN_OUT",
        item_id=lot.item_id,
        warehouse_id=lot.warehouse_id,
        location_id=lot.location_id,
        lot_id=lot.id,
        qty_change=-required_qty,
        weight_change=-required_weight,
        unit_cost=to_decimal(lot.unit_cost, "0.0001"),
        source_doc_type="RETURN_DISPOSITION",
        source_doc_id=disposition.id,
        source_line_id=disposition.id,
        biz_time=disposition.closed_at or datetime.now(),
        operator_user_id=context.user.id,
        remark="退货处置出库",
        amount_basis="QTY",
    )

    return_item.disposition_status = "CLOSED"
    db.add(return_item)
    sync_return_order_status(db, return_item.return_order_id)
    db.commit()
    archive_fields = _generate_return_archive_fields(db, business_id=int(txn.id), created_by=context.user.id)

    row = db.execute(get_return_dispositions_query(limit=1).where(ReturnDisposition.id == disposition.id)).mappings().first()
    if not row:
        raise HTTPException(status_code=500, detail="退货处置创建后读取失败")
    return _apply_archive_fields_to_model(ReturnDispositionRead.model_validate(dict(row)), archive_fields)


@router.post("/rework-outbound", response_model=ReturnDispositionRead)
def create_return_rework_outbound(
    payload: ReturnDispositionCreate,
    context: AuthContext = Depends(require_authenticated_user),
    db: Session = Depends(get_db),
) -> ReturnDispositionRead:
    if payload.disposition_type.upper() != "REWORK":
        raise HTTPException(status_code=400, detail="退货库出库只支持返工出库")
    return create_return_disposition(payload, context=context, db=db)
