from __future__ import annotations

import logging
import uuid
from collections import OrderedDict
from datetime import date, datetime
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
from typing import Any, Dict, Iterable, List, Tuple, Optional

from django.contrib.auth import get_user_model
from django.db import transaction
from django.utils import timezone

from basic_info import models as basic_info_models
from stock import models as stock_models
from stock.services import StockFlowService
from basic_info.services import MerchantSettingService

from .amounts import normalize_money_amount
from . import models
from .tasks import (
    create_purchase_order_stock_entries,
    create_sales_order_stock_entries,
    create_purchase_return_order_stock_entries,
    create_sales_return_order_stock_entries,
)
from .notifications import enqueue_business_order_speech


class BalanceService:
    @staticmethod
    def adjust_supplier_balance(
        *,
        merchant: basic_info_models.Merchant,
        supplier: basic_info_models.Supplier,
        delta: Decimal,
        source_type: models.BalanceChangeSourceEnum,
        source_id: int,
        request_id: str | None = None,
        remarks: str | None = '',
        extra_meta: Dict[str, Any] | None = None,
        red_flush_id=None,
    ) -> models.BalanceChangeRecord:
        return BalanceService._adjust_balance(
            merchant=merchant,
            counterparty=supplier,
            balance_model=models.SupplierBalance,
            balance_field='supplier',
            delta=delta,
            target_type=models.BalanceChangeTargetEnum.SUPPLIER,
            source_type=source_type,
            source_id=source_id,
            request_id=request_id,
            remarks=remarks,
            extra_meta=extra_meta,
            red_flush_id=red_flush_id,
        )

    @staticmethod
    def adjust_customer_balance(
        *,
        merchant: basic_info_models.Merchant,
        customer: basic_info_models.Customer,
        delta: Decimal,
        source_type: models.BalanceChangeSourceEnum,
        source_id: int,
        request_id: str | None = None,
        remarks: str | None = '',
        extra_meta: Dict[str, Any] | None = None,
        red_flush_id=None,
    ) -> models.BalanceChangeRecord:
        return BalanceService._adjust_balance(
            merchant=merchant,
            counterparty=customer,
            balance_model=models.CustomerBalance,
            balance_field='customer',
            delta=delta,
            target_type=models.BalanceChangeTargetEnum.CUSTOMER,
            source_type=source_type,
            source_id=source_id,
            request_id=request_id,
            remarks=remarks,
            extra_meta=extra_meta,
            red_flush_id=red_flush_id,
        )

    @staticmethod
    def _adjust_balance(
        *,
        merchant: basic_info_models.Merchant,
        counterparty,
        balance_model,
        balance_field: str,
        delta: Decimal,
        target_type: models.BalanceChangeTargetEnum,
        source_type: models.BalanceChangeSourceEnum,
        source_id: int,
        request_id: str | None,
        remarks: str | None,
        extra_meta: Dict[str, Any] | None,
        red_flush_id,
    ) -> models.BalanceChangeRecord:
        meta_payload = extra_meta or {}
        remarks_value = remarks or ''
        with transaction.atomic():
            balance, _ = balance_model.objects.select_for_update().get_or_create(
                merchant=merchant,
                defaults={'balance': Decimal('0')},
                **{balance_field: counterparty},
            )
            before = balance.balance
            balance.balance += delta
            balance.save(update_fields=['balance', 'updated_at'])

            record_kwargs = {
                'merchant': merchant,
                'target_type': target_type,
                'source_type': source_type,
                'source_id': source_id,
                'delta': delta,
                'direction': (
                    models.BalanceChangeDirectionEnum.INCREASE
                    if delta >= 0
                    else models.BalanceChangeDirectionEnum.DECREASE
                ),
                'balance_before': before,
                'balance_after': balance.balance,
                'request_id': request_id,
                'remarks': remarks_value,
                'extra_meta': meta_payload,
                'red_flush_id': red_flush_id,
            }
            if target_type == models.BalanceChangeTargetEnum.SUPPLIER:
                record_kwargs['supplier'] = counterparty
            else:
                record_kwargs['customer'] = counterparty

            return models.BalanceChangeRecord.objects.create(**record_kwargs)

    @staticmethod
    def get_customer_balance(
        *,
        merchant: basic_info_models.Merchant,
        customer: basic_info_models.Customer,
    ) -> Decimal:
        balance = models.CustomerBalance.objects.filter(
            merchant=merchant,
            customer=customer,
        ).first()
        if balance is None:
            return Decimal('0')
        return balance.balance

    @staticmethod
    def get_customer_statement_balance(
        *,
        merchant: basic_info_models.Merchant,
        customer: basic_info_models.Customer,
    ) -> Decimal:
        """
        返回客户对账单口径的当前余额。

        与 get_customer_balance 不同，这里会叠加 ExternalCustomerStatementOrder
        中尚未写入本地 CustomerBalance 的外部销售/退货净额，使 customer balance API
        与 statements API 的 current_balance 保持一致。
        """
        return BalanceService.get_customer_balance(
            merchant=merchant,
            customer=customer,
        ) + BalanceService.get_customer_external_statement_balance_adjustment(
            merchant=merchant,
            customer=customer,
        )

    @staticmethod
    def get_customer_external_statement_balance_adjustment(
        *,
        merchant: basic_info_models.Merchant,
        customer: basic_info_models.Customer,
    ) -> Decimal:
        """
        计算外部客户对账单业务单据对余额的净影响。

        公式需与 _CustomerStatementBuilder._build_external_statement_records 保持一致：
        - 外部销售：毛额 - 折扣 - 现场收款
        - 外部销售退货：-(退货金额 + 退货折扣)
        """
        adjustment = Decimal('0')
        qs = models.ExternalCustomerStatementOrder.objects.filter(
            merchant=merchant,
            customer=customer,
        ).only('category', 'total_amount', 'zk_amount', 'sf_amount')
        for order in qs:
            sf = order.sf_amount or Decimal('0')
            zk = order.zk_amount or Decimal('0')
            if order.category == models.ExternalCustomerStatementCategoryEnum.SALE:
                adjustment += (order.total_amount or Decimal('0')) - zk - sf
            else:
                adjustment -= (order.total_amount or Decimal('0')) + zk
        return adjustment

    @staticmethod
    def get_supplier_balance(
        *,
        merchant: basic_info_models.Merchant,
        supplier: basic_info_models.Supplier,
    ) -> Decimal:
        balance = models.SupplierBalance.objects.filter(
            merchant=merchant,
            supplier=supplier,
        ).first()
        if balance is None:
            return Decimal('0')
        return balance.balance


logger = logging.getLogger(__name__)


def _normalize_sales_order_kind(value) -> int:
    """
    校验并归一化销售单类型（kind）。

    None / 空字符串视为未提供，回退到默认类型（大货 WHOLESALE）。
    其余值必须是 SalesOrderKindEnum 中合法的整数枚举。
    """
    if value in (None, ''):
        return models.SalesOrderKindEnum.WHOLESALE

    try:
        kind_value = int(value)
    except (TypeError, ValueError) as exc:
        raise ValueError('kind 必须为合法的销售单类型枚举值') from exc

    if kind_value not in models.SalesOrderKindEnum.values:
        raise ValueError('kind 不是合法的销售单类型枚举值')

    return kind_value


def _normalize_order_date(value) -> date:

    if isinstance(value, date):
        return value
    if isinstance(value, datetime):
        return value.date()
    if isinstance(value, str):
        try:
            return date.fromisoformat(value)
        except ValueError as exc:
            raise ValueError('order_date 格式不正确，应为 YYYY-MM-DD') from exc
    raise ValueError('order_date 格式不正确')


def create_purchase_order(
    *,
    merchant: basic_info_models.Merchant,
    supplier: basic_info_models.Supplier,
    order_date,
    warehouse: basic_info_models.WareHouse,
    operator: basic_info_models.Employee,
    items: List[Dict[str, Any]],
    remarks: str | None = '',
    created_by=None,
    from_pre_purchase_order_id: int | None = None,
) -> models.PurchaseOrder:
    """
    创建采购订单并触发异步创建入库单任务。

    Args:
        merchant: 采购单所属商户
        supplier: 供应商
        order_date: 订单日期 (date)
        warehouse: 入库仓库实例
        operator: 经办人
        items: 产品明细，字段会根据仓库模式校验
        remarks: 备注
        created_by: 创建者用户（可选，用于 stock 记录中的 created_by）
    """
    if not items:
        raise ValueError('items 不能为空')

    normalized_date = _normalize_order_date(order_date)
    purchase_items, stock_flow_items = _normalize_order_items(
        merchant=merchant,
        warehouse=warehouse,
        items=items,
        is_outgoing=False,
    )

    with transaction.atomic():
        purchase_order = models.PurchaseOrder.objects.create(
            merchant=merchant,
            supplier=supplier,
            balance_before_snapshot=BalanceService.get_supplier_balance(
                merchant=merchant, supplier=supplier,
            ),
            purchase_date=normalized_date,
            operator=operator,
            warehouse=warehouse,
            remarks=remarks,
            from_pre_purchase_order_id=from_pre_purchase_order_id,
        )
        bulk_objects = [
            models.PurchaseOrderItem(
                purchase_order=purchase_order,
                product=item_data['product'],
                price=item_data['price'],
                color=item_data.get('color'),
                quantity=item_data['quantity'],
                unit=item_data['unit'],
                spec=item_data.get('spec'),
                empty_diff_percent=item_data['empty_diff_percent'],
                quantity_of_rolls=item_data.get('quantity_of_rolls'),
                num_of_rolls=item_data['num_of_rolls'],
                batch_number=item_data.get('batch_number'),
                remarks=item_data.get('remarks'),
            )
            for item_data in purchase_items
        ]
        models.PurchaseOrderItem.objects.bulk_create(bulk_objects)

    purchase_order.refresh_from_db()
    enqueue_business_order_speech(order=purchase_order, order_label='采购单', action_label='创建')
    return purchase_order


def update_purchase_order(
    *,
    purchase_order: models.PurchaseOrder,
    supplier: basic_info_models.Supplier | None = None,
    order_date=None,
    warehouse: basic_info_models.WareHouse | None = None,
    items: List[Dict[str, Any]] | None = None,
    remarks: str | None = '',
    from_pre_purchase_order_id: int | None = None,
) -> models.PurchaseOrder:
    """
    更新采购订单（仅限审批中状态）。
    """
    _ensure_order_pending(purchase_order, models.PurchaseOrderStatusEnum.PENDING, '采购单')

    merchant = purchase_order.merchant
    new_supplier = supplier or purchase_order.supplier
    new_warehouse = warehouse or purchase_order.warehouse
    new_order_date = _normalize_order_date(order_date or purchase_order.purchase_date)
    remarks = remarks if remarks is not None else purchase_order.remarks

    if not items:
        raise ValueError('items 需要为非空数组')

    normalized_items, _ = _normalize_order_items(
        merchant=merchant,
        warehouse=new_warehouse,
        items=items,
        is_outgoing=False,
    )

    with transaction.atomic():
        purchase_order.supplier = new_supplier
        purchase_order.purchase_date = new_order_date
        purchase_order.warehouse = new_warehouse
        purchase_order.remarks = remarks
        if from_pre_purchase_order_id is not None:
            purchase_order.from_pre_purchase_order_id = from_pre_purchase_order_id
        purchase_order.save(
            update_fields=[
                'supplier',
                'purchase_date',
                'warehouse',
                'remarks',
                'from_pre_purchase_order_id',
                'updated_at',
            ]
        )

        purchase_order.items.all().delete()
        bulk_objects = [
            models.PurchaseOrderItem(
                purchase_order=purchase_order,
                product=item_data['product'],
                price=item_data['price'],
                color=item_data.get('color'),
                quantity=item_data['quantity'],
                unit=item_data['unit'],
                spec=item_data.get('spec'),
                empty_diff_percent=item_data['empty_diff_percent'],
                quantity_of_rolls=item_data.get('quantity_of_rolls'),
                num_of_rolls=item_data['num_of_rolls'],
                batch_number=item_data.get('batch_number'),
                remarks=item_data.get('remarks'),
            )
            for item_data in normalized_items
        ]
        models.PurchaseOrderItem.objects.bulk_create(bulk_objects)

    purchase_order.refresh_from_db()
    return purchase_order


def create_sales_order(
    *,
    merchant: basic_info_models.Merchant,
    customer: basic_info_models.Customer,
    order_date,
    warehouse: basic_info_models.WareHouse,
    operator: basic_info_models.Employee,
    items: List[Dict[str, Any]],
    remarks: str | None = '',
    created_by=None,
    from_pre_sales_order_id: int | None = None,
    kind: int | None = None,
) -> models.SalesOrder:
    """
    创建销售订单，后续审批通过后会触发出库任务。
    """
    if not items:
        raise ValueError('items 不能为空')

    normalized_kind = _normalize_sales_order_kind(kind)

    normalized_date = _normalize_order_date(order_date)

    sales_items, stock_flow_items = _normalize_order_items(
        merchant=merchant,
        warehouse=warehouse,
        items=items,
        is_outgoing=True,
        customer=customer,
    )

    with transaction.atomic():
        sales_order = models.SalesOrder.objects.create(
            merchant=merchant,
            customer=customer,
            balance_before_snapshot=BalanceService.get_customer_balance(
                merchant=merchant, customer=customer,
            ),
            sales_date=normalized_date,
            operator=operator,
            warehouse=warehouse,
            remarks=remarks,
            from_pre_sales_order_id=from_pre_sales_order_id,
            kind=normalized_kind,
        )

        bulk_objects = [
            models.SalesOrderItem(
                sales_order=sales_order,
                product=item_data['product'],
                price=item_data['price'],
                color=item_data.get('color'),
                quantity=item_data['quantity'],
                unit=item_data['unit'],
                spec=item_data.get('spec'),
                empty_diff_percent=item_data['empty_diff_percent'],
                quantity_of_rolls=item_data.get('quantity_of_rolls'),
                num_of_rolls=item_data['num_of_rolls'],
                consume_detail_ids=item_data.get('consume_detail_ids'),
                batch_number=item_data.get('batch_number'),
                order_quantity=item_data.get('order_quantity'),
                remarks=item_data.get('remarks'),
                printing_job=item_data.get('printing_job'),
            )
            for item_data in sales_items
        ]
        models.SalesOrderItem.objects.bulk_create(bulk_objects)

    sales_order.refresh_from_db()
    enqueue_business_order_speech(order=sales_order, order_label='销售单', action_label='创建')
    return sales_order


def update_sales_order(
    *,
    sales_order: models.SalesOrder,
    customer: basic_info_models.Customer | None = None,
    order_date=None,
    warehouse: basic_info_models.WareHouse | None = None,
    operator: basic_info_models.Employee | None = None,
    items: List[Dict[str, Any]] | None = None,
    remarks: str | None = '',
    kind: int | None = None,
) -> models.SalesOrder:
    """
    更新销售订单（仅限审批中状态）。
    """
    _ensure_order_pending(sales_order, models.SalesOrderStatusEnum.PENDING, '销售单')

    merchant = sales_order.merchant
    new_customer = customer or sales_order.customer
    new_warehouse = warehouse or sales_order.warehouse
    new_operator = operator or sales_order.operator
    new_order_date = _normalize_order_date(order_date or sales_order.sales_date)
    remarks = remarks if remarks is not None else sales_order.remarks
    # kind 未提供时保持原值；提供则校验后更新
    new_kind = sales_order.kind if kind in (None, '') else _normalize_sales_order_kind(kind)


    if not items:
        raise ValueError('items 需要为非空数组')

    sales_items, stock_flow_items = _normalize_order_items(
        merchant=merchant,
        warehouse=new_warehouse,
        items=items,
        is_outgoing=True,
        customer=new_customer,
    )

    with transaction.atomic():
        sales_order.customer = new_customer
        sales_order.sales_date = new_order_date
        sales_order.warehouse = new_warehouse
        sales_order.operator = new_operator
        sales_order.remarks = remarks
        sales_order.kind = new_kind
        sales_order.save(
            update_fields=['customer', 'sales_date', 'warehouse', 'operator', 'remarks', 'kind', 'updated_at']
        )


        sales_order.items.all().delete()
        bulk_objects = [
            models.SalesOrderItem(
                sales_order=sales_order,
                product=item_data['product'],
                price=item_data['price'],
                color=item_data.get('color'),
                quantity=item_data['quantity'],
                unit=item_data['unit'],
                spec=item_data.get('spec'),
                empty_diff_percent=item_data['empty_diff_percent'],
                quantity_of_rolls=item_data.get('quantity_of_rolls'),
                num_of_rolls=item_data['num_of_rolls'],
                consume_detail_ids=item_data.get('consume_detail_ids'),
                batch_number=item_data.get('batch_number'),
                order_quantity=item_data.get('order_quantity'),
                remarks=item_data.get('remarks'),
                printing_job=item_data.get('printing_job'),
            )
            for item_data in sales_items
        ]
        models.SalesOrderItem.objects.bulk_create(bulk_objects)

    sales_order.refresh_from_db()
    return sales_order


def create_purchase_return_order(
    *,
    merchant: basic_info_models.Merchant,
    supplier: basic_info_models.Supplier,
    return_date,
    warehouse: basic_info_models.WareHouse,
    operator: basic_info_models.Employee,
    items: List[Dict[str, Any]],
    remarks: str | None = '',
    purchase_order: models.PurchaseOrder | None = None,
    purchase_order_id: int | None = None,
    created_by=None,
) -> models.PurchaseReturnOrder:
    """
    创建采购退货单，出库并冲减供应商欠款。
    """
    if not items:
        raise ValueError('items 不能为空')

    normalized_date = _normalize_order_date(return_date)
    normalized_items, _ = _normalize_order_items(
        merchant=merchant,
        warehouse=warehouse,
        items=items,
        is_outgoing=True,
    )

    resolved_purchase_order = purchase_order
    if resolved_purchase_order is None and purchase_order_id:
        resolved_purchase_order = models.PurchaseOrder.objects.filter(
            id=purchase_order_id,
            merchant=merchant,
        ).first()
        if resolved_purchase_order is None:
            raise ValueError('关联的采购单不存在或不属于当前商户')
    if resolved_purchase_order and resolved_purchase_order.supplier_id != supplier.id:
        raise ValueError('采购退货单的供应商必须与原采购单一致')

    with transaction.atomic():
        return_order = models.PurchaseReturnOrder.objects.create(
            merchant=merchant,
            supplier=supplier,
            balance_before_snapshot=BalanceService.get_supplier_balance(
                merchant=merchant, supplier=supplier,
            ),
            purchase_order=resolved_purchase_order,
            return_date=normalized_date,
            operator=operator,
            warehouse=warehouse,
            remarks=remarks,
        )
        bulk_objects = [
            models.PurchaseReturnOrderItem(
                purchase_return_order=return_order,
                product=item_data['product'],
                price=item_data['price'],
                color=item_data.get('color'),
                quantity=item_data['quantity'],
                unit=item_data['unit'],
                spec=item_data.get('spec'),
                empty_diff_percent=item_data['empty_diff_percent'],
                quantity_of_rolls=item_data.get('quantity_of_rolls'),
                num_of_rolls=item_data['num_of_rolls'],
                consume_detail_ids=item_data.get('consume_detail_ids'),
                batch_number=item_data.get('batch_number'),
                remarks=item_data.get('remarks'),
            )
            for item_data in normalized_items
        ]
        models.PurchaseReturnOrderItem.objects.bulk_create(bulk_objects)

    return_order.refresh_from_db()
    enqueue_business_order_speech(order=return_order, order_label='采购退货单', action_label='创建')
    return return_order


def update_purchase_return_order(
    *,
    purchase_return_order: models.PurchaseReturnOrder,
    supplier: basic_info_models.Supplier | None = None,
    return_date=None,
    warehouse: basic_info_models.WareHouse | None = None,
    items: List[Dict[str, Any]] | None = None,
    remarks: str | None = '',
    purchase_order: models.PurchaseOrder | None = None,
    purchase_order_id: int | None = None,
) -> models.PurchaseReturnOrder:
    """
    更新采购退货单（仅限审批中状态）。
    """
    _ensure_order_pending(purchase_return_order, models.PurchaseReturnStatusEnum.PENDING, '采购退货单')

    merchant = purchase_return_order.merchant
    new_supplier = supplier or purchase_return_order.supplier
    new_warehouse = warehouse or purchase_return_order.warehouse
    new_return_date = _normalize_order_date(return_date or purchase_return_order.return_date)
    remarks = remarks if remarks is not None else purchase_return_order.remarks

    if not items:
        raise ValueError('items 需要为非空数组')

    resolved_purchase_order = purchase_order or purchase_return_order.purchase_order
    if resolved_purchase_order is None and purchase_order_id:
        resolved_purchase_order = models.PurchaseOrder.objects.filter(
            id=purchase_order_id,
            merchant=merchant,
        ).first()
        if resolved_purchase_order is None:
            raise ValueError('关联的采购单不存在或不属于当前商户')
    if resolved_purchase_order and resolved_purchase_order.supplier_id != new_supplier.id:
        raise ValueError('采购退货单的供应商必须与原采购单一致')

    normalized_items, _ = _normalize_order_items(
        merchant=merchant,
        warehouse=new_warehouse,
        items=items,
        is_outgoing=True,
    )

    with transaction.atomic():
        purchase_return_order.supplier = new_supplier
        purchase_return_order.return_date = new_return_date
        purchase_return_order.warehouse = new_warehouse
        purchase_return_order.remarks = remarks
        purchase_return_order.purchase_order = resolved_purchase_order
        purchase_return_order.save(
            update_fields=['supplier', 'return_date', 'warehouse', 'remarks', 'purchase_order', 'updated_at']
        )

        purchase_return_order.items.all().delete()
        bulk_objects = [
            models.PurchaseReturnOrderItem(
                purchase_return_order=purchase_return_order,
                product=item_data['product'],
                price=item_data['price'],
                color=item_data.get('color'),
                quantity=item_data['quantity'],
                unit=item_data['unit'],
                spec=item_data.get('spec'),
                empty_diff_percent=item_data['empty_diff_percent'],
                quantity_of_rolls=item_data.get('quantity_of_rolls'),
                num_of_rolls=item_data['num_of_rolls'],
                consume_detail_ids=item_data.get('consume_detail_ids'),
                batch_number=item_data.get('batch_number'),
                remarks=item_data.get('remarks'),
            )
            for item_data in normalized_items
        ]
        models.PurchaseReturnOrderItem.objects.bulk_create(bulk_objects)

    purchase_return_order.refresh_from_db()
    return purchase_return_order


def create_sales_return_order(
    *,
    merchant: basic_info_models.Merchant,
    customer: basic_info_models.Customer,
    return_date,
    warehouse: basic_info_models.WareHouse,
    operator: basic_info_models.Employee,
    items: List[Dict[str, Any]],
    remarks: str | None = '',
    sales_order: models.SalesOrder | None = None,
    sales_order_id: int | None = None,
    created_by=None,
) -> models.SalesReturnOrder:
    """
    创建销售退货单，入库并冲减客户欠款。
    """
    if not items:
        raise ValueError('items 不能为空')

    normalized_date = _normalize_order_date(return_date)
    normalized_items, _ = _normalize_order_items(
        merchant=merchant,
        warehouse=warehouse,
        items=items,
        is_outgoing=False,
    )

    resolved_sales_order = sales_order
    if resolved_sales_order is None and sales_order_id:
        resolved_sales_order = models.SalesOrder.objects.filter(
            id=sales_order_id,
            merchant=merchant,
        ).first()
        if resolved_sales_order is None:
            raise ValueError('关联的销售单不存在或不属于当前商户')
    if resolved_sales_order and resolved_sales_order.customer_id != customer.id:
        raise ValueError('销售退货单的客户必须与原销售单一致')

    with transaction.atomic():
        return_order = models.SalesReturnOrder.objects.create(
            merchant=merchant,
            customer=customer,
            balance_before_snapshot=BalanceService.get_customer_balance(
                merchant=merchant, customer=customer,
            ),
            sales_order=resolved_sales_order,
            return_date=normalized_date,
            operator=operator,
            warehouse=warehouse,
            remarks=remarks,
        )
        bulk_objects = [
            models.SalesReturnOrderItem(
                sales_return_order=return_order,
                product=item_data['product'],
                price=item_data['price'],
                color=item_data.get('color'),
                quantity=item_data['quantity'],
                unit=item_data['unit'],
                spec=item_data.get('spec'),
                empty_diff_percent=item_data['empty_diff_percent'],
                quantity_of_rolls=item_data.get('quantity_of_rolls'),
                num_of_rolls=item_data['num_of_rolls'],
                consume_detail_ids=item_data.get('consume_detail_ids'),
                batch_number=item_data.get('batch_number'),
                remarks=item_data.get('remarks'),
            )
            for item_data in normalized_items
        ]
        models.SalesReturnOrderItem.objects.bulk_create(bulk_objects)

    return_order.refresh_from_db()
    enqueue_business_order_speech(order=return_order, order_label='销售退货单', action_label='创建')
    return return_order


def update_sales_return_order(
    *,
    sales_return_order: models.SalesReturnOrder,
    customer: basic_info_models.Customer | None = None,
    return_date=None,
    warehouse: basic_info_models.WareHouse | None = None,
    items: List[Dict[str, Any]] | None = None,
    remarks: str | None = '',
    sales_order: models.SalesOrder | None = None,
    sales_order_id: int | None = None,
) -> models.SalesReturnOrder:
    """
    更新销售退货单（仅限审批中状态）。
    """
    _ensure_order_pending(sales_return_order, models.SalesReturnStatusEnum.PENDING, '销售退货单')

    merchant = sales_return_order.merchant
    new_customer = customer or sales_return_order.customer
    new_warehouse = warehouse or sales_return_order.warehouse
    new_return_date = _normalize_order_date(return_date or sales_return_order.return_date)
    remarks = remarks if remarks is not None else sales_return_order.remarks

    if not items:
        raise ValueError('items 需要为非空数组')

    resolved_sales_order = sales_order or sales_return_order.sales_order
    if resolved_sales_order is None and sales_order_id:
        resolved_sales_order = models.SalesOrder.objects.filter(
            id=sales_order_id,
            merchant=merchant,
        ).first()
        if resolved_sales_order is None:
            raise ValueError('关联的销售单不存在或不属于当前商户')

    if resolved_sales_order and resolved_sales_order.customer_id != new_customer.id:
        raise ValueError('销售退货单的客户必须与原销售单一致')

    normalized_items, _ = _normalize_order_items(
        merchant=merchant,
        warehouse=new_warehouse,
        items=items,
        is_outgoing=False,
    )

    with transaction.atomic():
        sales_return_order.customer = new_customer
        sales_return_order.return_date = new_return_date
        sales_return_order.warehouse = new_warehouse
        sales_return_order.remarks = remarks
        sales_return_order.sales_order = resolved_sales_order
        sales_return_order.save(
            update_fields=['customer', 'return_date', 'warehouse', 'remarks', 'sales_order', 'updated_at']
        )

        sales_return_order.items.all().delete()
        bulk_objects = [
            models.SalesReturnOrderItem(
                sales_return_order=sales_return_order,
                product=item_data['product'],
                price=item_data['price'],
                color=item_data.get('color'),
                quantity=item_data['quantity'],
                unit=item_data['unit'],
                spec=item_data.get('spec'),
                empty_diff_percent=item_data['empty_diff_percent'],
                quantity_of_rolls=item_data.get('quantity_of_rolls'),
                num_of_rolls=item_data['num_of_rolls'],
                consume_detail_ids=item_data.get('consume_detail_ids'),
                batch_number=item_data.get('batch_number'),
                remarks=item_data.get('remarks'),
            )
            for item_data in normalized_items
        ]
        models.SalesReturnOrderItem.objects.bulk_create(bulk_objects)

    sales_return_order.refresh_from_db()
    return sales_return_order


def create_payment_order(
    *,
    merchant: basic_info_models.Merchant,
    supplier: basic_info_models.Supplier,
    payment_date,
    amount,
    operator: basic_info_models.Employee,
    remarks: str | None = '',
    bank_account: basic_info_models.BankAccount | None = None,
    markup: str | None = None,
    discount_amount=None,
) -> models.PaymentOrder:
    """
    创建付款单（资金流出）。
    """
    normalized_date = _normalize_order_date(payment_date)
    normalized_amount = _ensure_non_zero_amount(amount, 'amount')
    normalized_discount = _ensure_non_negative_amount(discount_amount, 'discount_amount')

    if bank_account and bank_account.merchant_id != merchant.id:
        raise ValueError('银行账户不属于当前商户')

    with transaction.atomic():
        payment_order = models.PaymentOrder.objects.create(
            merchant=merchant,
            supplier=supplier,
            payment_date=normalized_date,
            amount=normalized_amount,
            operator=operator,
            remarks=remarks,
            bank_account=bank_account,
            markup=markup or None,
            discount_amount=normalized_discount,
        )
    payment_order.refresh_from_db()
    enqueue_business_order_speech(order=payment_order, order_label='付款单', action_label='创建')
    return payment_order


def create_receipt_order(
    *,
    merchant: basic_info_models.Merchant,
    customer: basic_info_models.Customer,
    receipt_date,
    amount,
    operator: basic_info_models.Employee,
    remarks: str | None = '',
    bank_account: basic_info_models.BankAccount | None = None,
    markup: str | None = None,
    discount_amount=None,
) -> models.ReceiptOrder:
    """
    创建收款单（资金流入）。
    """
    normalized_date = _normalize_order_date(receipt_date)
    normalized_amount = _ensure_non_zero_amount(amount, 'amount')
    normalized_discount = _ensure_non_negative_amount(discount_amount, 'discount_amount')

    if bank_account and bank_account.merchant_id != merchant.id:
        raise ValueError('银行账户不属于当前商户')

    with transaction.atomic():
        receipt_order = models.ReceiptOrder.objects.create(
            merchant=merchant,
            customer=customer,
            receipt_date=normalized_date,
            amount=normalized_amount,
            operator=operator,
            remarks=remarks,
            bank_account=bank_account,
            markup=markup or None,
            discount_amount=normalized_discount,
        )
    receipt_order.refresh_from_db()
    enqueue_business_order_speech(order=receipt_order, order_label='收款单', action_label='创建')
    return receipt_order


def review_purchase_order(
    *,
    purchase_order: models.PurchaseOrder | None = None,
    purchase_order_id: int | None = None,
    target_status: models.PurchaseOrderStatusEnum,
    reviewed_by=None,
) -> models.PurchaseOrder:
    """
    审批或作废采购单。

    当目标状态为 APPROVED 且开启自动入库任务时，将触发入库 Celery 任务；
    当目标状态为 CANCELLED 时，会在更新状态前确认未生成任何出入库单。
    """
    order = _resolve_purchase_order_instance(purchase_order, purchase_order_id)

    if target_status not in {
        models.PurchaseOrderStatusEnum.APPROVED,
        models.PurchaseOrderStatusEnum.CANCELLED,
    }:
        raise ValueError('target_status 只能是 APPROVED 或 CANCELLED')

    if order.status == target_status:
        return order

    if target_status == models.PurchaseOrderStatusEnum.APPROVED:
        if order.status == models.PurchaseOrderStatusEnum.CANCELLED:
            raise ValueError('作废状态的采购单无法再次审批')
        return _approve_purchase_order(order, reviewed_by)

    return _cancel_purchase_order(order)


def review_sales_order(
    *,
    sales_order: models.SalesOrder | None = None,
    sales_order_id: int | None = None,
    target_status: models.SalesOrderStatusEnum,
    reviewed_by=None,
) -> models.SalesOrder:
    """
    审批或作废销售单。
    """
    order = _resolve_sales_order_instance(sales_order, sales_order_id)

    if target_status not in {
        models.SalesOrderStatusEnum.APPROVED,
        models.SalesOrderStatusEnum.CANCELLED,
    }:
        raise ValueError('target_status 只能是 APPROVED 或 CANCELLED')

    if order.status == target_status:
        return order

    if target_status == models.SalesOrderStatusEnum.APPROVED:
        if order.status == models.SalesOrderStatusEnum.CANCELLED:
            raise ValueError('作废状态的销售单无法再次审批')
        return _approve_sales_order(order, reviewed_by)

    return _cancel_sales_order(order)


def review_purchase_return_order(
    *,
    purchase_return_order: models.PurchaseReturnOrder | None = None,
    purchase_return_order_id: int | None = None,
    target_status: models.PurchaseReturnStatusEnum,
    reviewed_by=None,
) -> models.PurchaseReturnOrder:
    order = _resolve_purchase_return_order_instance(purchase_return_order, purchase_return_order_id)

    if target_status not in {
        models.PurchaseReturnStatusEnum.APPROVED,
        models.PurchaseReturnStatusEnum.CANCELLED,
    }:
        raise ValueError('target_status 只能是 APPROVED 或 CANCELLED')

    if order.status == target_status:
        return order

    if target_status == models.PurchaseReturnStatusEnum.APPROVED:
        if order.status == models.PurchaseReturnStatusEnum.CANCELLED:
            raise ValueError('作废状态的采购退货单无法再次审批')
        return _approve_purchase_return_order(order, reviewed_by)

    return _cancel_purchase_return_order(order)


def review_sales_return_order(
    *,
    sales_return_order: models.SalesReturnOrder | None = None,
    sales_return_order_id: int | None = None,
    target_status: models.SalesReturnStatusEnum,
    reviewed_by=None,
) -> models.SalesReturnOrder:
    order = _resolve_sales_return_order_instance(sales_return_order, sales_return_order_id)

    if target_status not in {
        models.SalesReturnStatusEnum.APPROVED,
        models.SalesReturnStatusEnum.CANCELLED,
    }:
        raise ValueError('target_status 只能是 APPROVED 或 CANCELLED')

    if order.status == target_status:
        return order

    if target_status == models.SalesReturnStatusEnum.APPROVED:
        if order.status == models.SalesReturnStatusEnum.CANCELLED:
            raise ValueError('作废状态的销售退货单无法再次审批')
        return _approve_sales_return_order(order, reviewed_by)

    return _cancel_sales_return_order(order)


def review_payment_order(
    *,
    payment_order: models.PaymentOrder | None = None,
    payment_order_id: int | None = None,
    target_status: models.PaymentOrderStatusEnum,
    reviewed_by=None,
) -> models.PaymentOrder:
    order = _resolve_payment_order_instance(payment_order, payment_order_id)

    if target_status not in {
        models.PaymentOrderStatusEnum.APPROVED,
        models.PaymentOrderStatusEnum.CANCELLED,
    }:
        raise ValueError('target_status 只能是 APPROVED 或 CANCELLED')

    if order.status == target_status:
        return order

    if target_status == models.PaymentOrderStatusEnum.APPROVED:
        if order.status == models.PaymentOrderStatusEnum.CANCELLED:
            raise ValueError('作废状态的付款单无法再次审批')
        with transaction.atomic():
            locked = models.PaymentOrder.objects.select_related(
                'merchant', 'supplier'
            ).select_for_update().get(id=order.id)
            if locked.status == models.PaymentOrderStatusEnum.APPROVED:
                return locked
            if locked.status == models.PaymentOrderStatusEnum.CANCELLED:
                raise ValueError('作废状态的付款单无法再次审批')
            locked.status = models.PaymentOrderStatusEnum.APPROVED
            locked.save(update_fields=['status', 'updated_at'])
            BalanceService.adjust_supplier_balance(
                merchant=locked.merchant,
                supplier=locked.supplier,
                delta=-locked.settlement_amount,
                source_type=models.BalanceChangeSourceEnum.PAYMENT_ORDER,
                source_id=locked.id,
            )
        locked.refresh_from_db(fields=['status', 'updated_at'])
        return locked

    return _cancel_order_impl(
        order=order,
        order_model_cls=models.PaymentOrder,
        approved_status=models.PaymentOrderStatusEnum.APPROVED,
        cancelled_status=models.PaymentOrderStatusEnum.CANCELLED,
        error_label='付款单',
    )


def review_receipt_order(
    *,
    receipt_order: models.ReceiptOrder | None = None,
    receipt_order_id: int | None = None,
    target_status: models.ReceiptOrderStatusEnum,
    reviewed_by=None,
) -> models.ReceiptOrder:
    order = _resolve_receipt_order_instance(receipt_order, receipt_order_id)

    if target_status not in {
        models.ReceiptOrderStatusEnum.APPROVED,
        models.ReceiptOrderStatusEnum.CANCELLED,
    }:
        raise ValueError('target_status 只能是 APPROVED 或 CANCELLED')

    if order.status == target_status:
        return order

    if target_status == models.ReceiptOrderStatusEnum.APPROVED:
        if order.status == models.ReceiptOrderStatusEnum.CANCELLED:
            raise ValueError('作废状态的收款单无法再次审批')
        with transaction.atomic():
            locked = models.ReceiptOrder.objects.select_related(
                'merchant', 'customer'
            ).select_for_update().get(id=order.id)
            if locked.status == models.ReceiptOrderStatusEnum.APPROVED:
                return locked
            if locked.status == models.ReceiptOrderStatusEnum.CANCELLED:
                raise ValueError('作废状态的收款单无法再次审批')
            locked.status = models.ReceiptOrderStatusEnum.APPROVED
            locked.save(update_fields=['status', 'updated_at'])
            BalanceService.adjust_customer_balance(
                merchant=locked.merchant,
                customer=locked.customer,
                delta=-locked.settlement_amount,
                source_type=models.BalanceChangeSourceEnum.RECEIPT_ORDER,
                source_id=locked.id,
            )
        locked.refresh_from_db(fields=['status', 'updated_at'])
        return locked

    return _cancel_order_impl(
        order=order,
        order_model_cls=models.ReceiptOrder,
        approved_status=models.ReceiptOrderStatusEnum.APPROVED,
        cancelled_status=models.ReceiptOrderStatusEnum.CANCELLED,
        error_label='收款单',
    )


def red_flush_purchase_order(
    *,
    merchant: basic_info_models.Merchant,
    purchase_order: models.PurchaseOrder | None = None,
    purchase_order_id: int | None = None,
    red_flushed_by=None,
    reason: str | None = '',
) -> models.PurchaseOrder:
    order = _resolve_purchase_order_instance(purchase_order, purchase_order_id)
    _ensure_order_merchant(order, merchant, '采购单')
    return _red_flush_order_impl(
        order=order,
        order_model_cls=models.PurchaseOrder,
        approved_status=models.PurchaseOrderStatusEnum.APPROVED,
        balance_source_type=models.BalanceChangeSourceEnum.PURCHASE_ORDER,
        stock_source_type=stock_models.StockChangeSourceEnum.PURCHASE,
        counterparty_field='supplier',
        error_label='采购单',
        red_flushed_by=red_flushed_by,
        reason=reason,
    )


def red_flush_sales_order(
    *,
    merchant: basic_info_models.Merchant,
    sales_order: models.SalesOrder | None = None,
    sales_order_id: int | None = None,
    red_flushed_by=None,
    reason: str | None = '',
) -> models.SalesOrder:
    order = _resolve_sales_order_instance(sales_order, sales_order_id)
    _ensure_order_merchant(order, merchant, '销售单')
    return _red_flush_order_impl(
        order=order,
        order_model_cls=models.SalesOrder,
        approved_status=models.SalesOrderStatusEnum.APPROVED,
        balance_source_type=models.BalanceChangeSourceEnum.SALES_ORDER,
        stock_source_type=stock_models.StockChangeSourceEnum.SALES,
        require_stock_records=False,
        counterparty_field='customer',
        error_label='销售单',
        red_flushed_by=red_flushed_by,
        reason=reason,
    )


def red_flush_purchase_return_order(
    *,
    merchant: basic_info_models.Merchant,
    purchase_return_order: models.PurchaseReturnOrder | None = None,
    purchase_return_order_id: int | None = None,
    red_flushed_by=None,
    reason: str | None = '',
) -> models.PurchaseReturnOrder:
    order = _resolve_purchase_return_order_instance(purchase_return_order, purchase_return_order_id)
    _ensure_order_merchant(order, merchant, '采购退货单')
    return _red_flush_order_impl(
        order=order,
        order_model_cls=models.PurchaseReturnOrder,
        approved_status=models.PurchaseReturnStatusEnum.APPROVED,
        balance_source_type=models.BalanceChangeSourceEnum.PURCHASE_RETURN_ORDER,
        stock_source_type=stock_models.StockChangeSourceEnum.PURCHASE_RETURN,
        counterparty_field='supplier',
        error_label='采购退货单',
        red_flushed_by=red_flushed_by,
        reason=reason,
    )


def red_flush_sales_return_order(
    *,
    merchant: basic_info_models.Merchant,
    sales_return_order: models.SalesReturnOrder | None = None,
    sales_return_order_id: int | None = None,
    red_flushed_by=None,
    reason: str | None = '',
) -> models.SalesReturnOrder:
    order = _resolve_sales_return_order_instance(sales_return_order, sales_return_order_id)
    _ensure_order_merchant(order, merchant, '销售退货单')
    return _red_flush_order_impl(
        order=order,
        order_model_cls=models.SalesReturnOrder,
        approved_status=models.SalesReturnStatusEnum.APPROVED,
        balance_source_type=models.BalanceChangeSourceEnum.SALES_RETURN_ORDER,
        stock_source_type=stock_models.StockChangeSourceEnum.SALES_RETURN,
        counterparty_field='customer',
        error_label='销售退货单',
        red_flushed_by=red_flushed_by,
        reason=reason,
    )


def red_flush_payment_order(
    *,
    merchant: basic_info_models.Merchant,
    payment_order: models.PaymentOrder | None = None,
    payment_order_id: int | None = None,
    red_flushed_by=None,
    reason: str | None = '',
) -> models.PaymentOrder:
    order = _resolve_payment_order_instance(payment_order, payment_order_id)
    _ensure_order_merchant(order, merchant, '付款单')
    return _red_flush_order_impl(
        order=order,
        order_model_cls=models.PaymentOrder,
        approved_status=models.PaymentOrderStatusEnum.APPROVED,
        balance_source_type=models.BalanceChangeSourceEnum.PAYMENT_ORDER,
        stock_source_type=None,
        counterparty_field='supplier',
        error_label='付款单',
        red_flushed_by=red_flushed_by,
        reason=reason,
    )


def red_flush_receipt_order(
    *,
    merchant: basic_info_models.Merchant,
    receipt_order: models.ReceiptOrder | None = None,
    receipt_order_id: int | None = None,
    red_flushed_by=None,
    reason: str | None = '',
) -> models.ReceiptOrder:
    order = _resolve_receipt_order_instance(receipt_order, receipt_order_id)
    _ensure_order_merchant(order, merchant, '收款单')
    return _red_flush_order_impl(
        order=order,
        order_model_cls=models.ReceiptOrder,
        approved_status=models.ReceiptOrderStatusEnum.APPROVED,
        balance_source_type=models.BalanceChangeSourceEnum.RECEIPT_ORDER,
        stock_source_type=None,
        counterparty_field='customer',
        error_label='收款单',
        red_flushed_by=red_flushed_by,
        reason=reason,
    )


def _ensure_order_merchant(order, merchant: basic_info_models.Merchant, error_label: str) -> None:
    if merchant is None:
        raise ValueError('必须提供 merchant')
    if order.merchant_id != merchant.id:
        raise ValueError(f'{error_label}不属于当前商户')


def _red_flush_order_impl(
    *,
    order,
    order_model_cls,
    approved_status,
    balance_source_type: models.BalanceChangeSourceEnum,
    stock_source_type: stock_models.StockChangeSourceEnum | None,
    counterparty_field: str,
    error_label: str,
    require_stock_records: bool = True,
    red_flushed_by=None,
    reason: str | None = '',
):
    reason_text = (reason or '').strip() or f'红冲{error_label}'
    current_red_flush_id = uuid.uuid4()

    with transaction.atomic():
        locked_order = order_model_cls.objects.select_for_update().get(id=order.id)
        if locked_order.status != approved_status:
            raise ValueError(f'仅允许红冲已审批的{error_label}')
        if locked_order.is_red_flushed:
            raise ValueError(f'{error_label}已红冲，不能重复红冲')
        _ensure_order_not_external_source(locked_order, error_label)

        original_balance_record = _get_original_balance_change_record(
            merchant_id=locked_order.merchant_id,
            source_type=balance_source_type,
            source_id=locked_order.id,
            error_label=error_label,
        )

        reverse_record = _create_reverse_balance_change_record(
            original_record=original_balance_record,
            order=locked_order,
            counterparty_field=counterparty_field,
            red_flush_id=current_red_flush_id,
            reason=reason_text,
        )

        now = timezone.now()
        original_balance_record.cancelled = True
        original_balance_record.cancelled_at = now
        original_balance_record.offset_id = reverse_record.id
        original_balance_record.red_flush_id = current_red_flush_id
        original_balance_record.save(update_fields=['cancelled', 'cancelled_at', 'offset_id', 'red_flush_id', 'updated_at'])

        reverse_record.offset_to = original_balance_record.id
        reverse_record.offset_at = now
        reverse_record.red_flush_id = current_red_flush_id
        reverse_record.save(update_fields=['offset_to', 'offset_at', 'red_flush_id', 'updated_at'])

        if stock_source_type is not None:
            _red_flush_stock_records_for_order(
                order=locked_order,
                stock_source_type=stock_source_type,
                red_flush_id=current_red_flush_id,
                red_flushed_by=red_flushed_by,
                reason=reason_text,
                error_label=error_label,
                require_stock_records=require_stock_records,
            )

        locked_order.is_red_flushed = True
        locked_order.red_flush_id = current_red_flush_id
        locked_order.red_flushed_at = now
        locked_order.save(update_fields=['is_red_flushed', 'red_flush_id', 'red_flushed_at', 'updated_at'])

    locked_order.refresh_from_db()
    return locked_order


def _ensure_order_not_external_source(order, error_label: str) -> None:
    if getattr(order, 'is_external_source', False) or getattr(order, 'external_source_id', None):
        raise ValueError(f'外部来源{error_label}不允许红冲')


def _get_original_balance_change_record(
    *,
    merchant_id: int,
    source_type: models.BalanceChangeSourceEnum,
    source_id: int,
    error_label: str,
) -> models.BalanceChangeRecord:
    qs = models.BalanceChangeRecord.objects.select_for_update().filter(
        merchant_id=merchant_id,
        source_type=source_type,
        source_id=source_id,
        offset_to__isnull=True,
        cancelled=False,
    )
    try:
        return qs.get()
    except models.BalanceChangeRecord.DoesNotExist as exc:
        raise ValueError(f'{error_label}缺少可红冲的余额变动记录') from exc
    except models.BalanceChangeRecord.MultipleObjectsReturned as exc:
        raise ValueError(f'{error_label}存在多条可红冲的余额变动记录') from exc


def _create_reverse_balance_change_record(
    *,
    original_record: models.BalanceChangeRecord,
    order,
    counterparty_field: str,
    red_flush_id,
    reason: str,
) -> models.BalanceChangeRecord:
    reverse_delta = -original_record.delta
    remarks = f'红冲余额记录 {original_record.id}: {reason}'
    extra_meta = {
        'red_flush_of_balance_change_record': original_record.id,
    }
    if counterparty_field == 'supplier':
        return BalanceService.adjust_supplier_balance(
            merchant=order.merchant,
            supplier=order.supplier,
            delta=reverse_delta,
            source_type=original_record.source_type,
            source_id=original_record.source_id,
            remarks=remarks,
            extra_meta=extra_meta,
            red_flush_id=red_flush_id,
        )
    if counterparty_field == 'customer':
        return BalanceService.adjust_customer_balance(
            merchant=order.merchant,
            customer=order.customer,
            delta=reverse_delta,
            source_type=original_record.source_type,
            source_id=original_record.source_id,
            remarks=remarks,
            extra_meta=extra_meta,
            red_flush_id=red_flush_id,
        )
    raise ValueError('未知的业务主体类型')


def _red_flush_stock_records_for_order(
    *,
    order,
    stock_source_type: stock_models.StockChangeSourceEnum,
    red_flush_id,
    red_flushed_by,
    reason: str,
    error_label: str,
    require_stock_records: bool = True,
) -> None:
    stock_records = list(
        stock_models.StockChangeRecord.objects.select_for_update().filter(
            merchant_id=order.merchant_id,
            source_type=stock_source_type,
            source_id=order.id,
        ).order_by('id')
    )
    if not stock_records:
        if require_stock_records:
            raise ValueError(f'{error_label}缺少可红冲的库存记录')
        return

    stock_service = StockFlowService(merchant=order.merchant, created_by=red_flushed_by)
    for stock_record in stock_records:
        stock_service.offset_stock_change(
            source_record_id=stock_record.id,
            reason=reason,
            red_flush_id=red_flush_id,
            extra_meta={
                'business_order_type': order.__class__.__name__,
                'business_order_id': order.id,
            },
        )


def _normalize_order_items(
    *,
    merchant: basic_info_models.Merchant,
    warehouse: basic_info_models.WareHouse,
    items: List[Dict[str, Any]],
    is_outgoing: bool,
    customer: Optional[basic_info_models.Customer] = None,
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
    """
    根据仓库模式校验订单明细，并返回：
    - normalized_items: 用于创建订单明细模型
    - stock_flow_items: 传递给 StockFlowService 的 items 结构
    is_outgoing=True 表示出库（销售等），需要额外校验 consume_detail_ids。
    """
    normalized_items: List[Dict[str, Any]] = []
    stock_flow_items: List[Dict[str, Any]] = []

    warehouse_mode = warehouse.mode

    for index, raw_item in enumerate(items):
        product_id = raw_item.get('product_id')
        if not product_id:
            raise ValueError(f'items[{index}].product_id 不能为空')
        try:
            product = basic_info_models.Product.objects.get(id=product_id, merchant=merchant)
        except basic_info_models.Product.DoesNotExist as exc:
            raise ValueError(f'产品 {product_id} 不存在或不属于当前商户') from exc

        price = _to_decimal(raw_item.get('price', '0'), f'items[{index}].price')
        empty_diff_percent = _to_decimal(raw_item.get('empty_diff_percent', '0'), f'items[{index}].empty_diff_percent')
        color = raw_item.get('color')
        batch_number = raw_item.get('batch_number')
        remarks = raw_item.get('remarks')
        spec = raw_item.get('spec')
        order_quantity = _to_non_negative_int_or_none(
            raw_item.get('order_quantity'),
            f'items[{index}].order_quantity',
        )
        unit = raw_item.get('unit') or product.get_unit_display() or '米'
        printing_job = None
        raw_printing_job_id = raw_item.get('printing_job') or raw_item.get('printing_job_id')
        if raw_printing_job_id is not None:
            try:
                printing_job_id = int(raw_printing_job_id)
            except (TypeError, ValueError):
                raise ValueError(f'items[{index}].printing_job 必须为数字')
            from printing import models as printing_models
            try:
                printing_job = printing_models.PrintingJob.objects.select_related(
                    'product', 'printing_order__customer'
                ).get(id=printing_job_id)
            except printing_models.PrintingJob.DoesNotExist as exc:
                raise ValueError(f'items[{index}].printing_job 不存在或已删除') from exc

            if printing_job.product_id != product.id:
                raise ValueError(f'items[{index}].printing_job 对应的产品与当前明细不一致')
            if customer and printing_job.printing_order and printing_job.printing_order.customer_id != customer.id:
                raise ValueError(f'items[{index}].printing_job 客户不匹配')

        quantity = 0
        num_of_rolls = 0
        quantity_of_rolls = None
        consume_detail_ids_str = None

        if warehouse_mode == basic_info_models.WareHouseModeEnum.UNRESTRICTED:
            if 'numbers' in raw_item and raw_item['numbers']:
                raise ValueError(f'仓库为宽进/宽出模式，items[{index}] 不应提供 numbers')
            quantity = _to_positive_int(raw_item.get('quantity'), f'items[{index}].quantity')
            num_of_rolls = _to_positive_int(raw_item.get('num_of_rolls'), f'items[{index}].num_of_rolls')
            stock_flow_items.append({
                'product_id': product.id,
                'value': str(quantity),
                'num_of_rolls': num_of_rolls,
            })
        elif warehouse_mode == basic_info_models.WareHouseModeEnum.RESTRICT_IN:
            numbers = raw_item.get('numbers')
            if not numbers or not isinstance(numbers, list):
                raise ValueError(f'仓库为严进宽出模式，items[{index}] 需要提供 numbers 数组')
            normalized_numbers = [
                str(_to_positive_int(value, f'items[{index}].numbers[{pos}]'))
                for pos, value in enumerate(numbers)
            ]
            num_of_rolls = len(normalized_numbers)
            quantity = sum(int(val) for val in normalized_numbers)
            quantity_of_rolls = ','.join(normalized_numbers)
            stock_flow_items.append({
                'product_id': product.id,
                'quantities': normalized_numbers,
            })
        else:  # WareHouseModeEnum.RESTRICT_IN_OUT
            if is_outgoing:
                consume_ids = raw_item.get('consume_detail_ids')
                if not consume_ids or not isinstance(consume_ids, list):
                    raise ValueError(f'仓库为严进严出模式，items[{index}] 需要提供 consume_detail_ids 数组')
                normalized_ids = [
                    _to_positive_int(value, f'items[{index}].consume_detail_ids[{pos}]')
                    for pos, value in enumerate(consume_ids)
                ]
                consume_detail_ids_str = ','.join(str(value) for value in normalized_ids)
                quantity = _to_positive_int(raw_item.get('quantity'), f'items[{index}].quantity')
                num_of_rolls = len(normalized_ids)
                details_by_id = stock_models.StockChangeDetail.objects.select_related(
                    'stock_change_record'
                ).filter(
                    id__in=normalized_ids,
                    merchant=merchant,
                ).in_bulk()
                if len(details_by_id) == len(normalized_ids):
                    quantities: List[str] = []
                    for pos, detail_id in enumerate(normalized_ids):
                        detail = details_by_id[detail_id]
                        if detail.product_id != product.id:
                            raise ValueError(
                                f'items[{index}].consume_detail_ids[{pos}] 产品不匹配'
                            )
                        if detail.stock_change_record.warehouse_id != warehouse.id:
                            raise ValueError(
                                f'items[{index}].consume_detail_ids[{pos}] 仓库不匹配'
                            )
                        quantities.append(format(detail.quantity.normalize(), 'f'))
                    quantity_of_rolls = ','.join(quantities)
                stock_flow_items.append({
                    'product_id': product.id,
                    'consume_detail_ids': normalized_ids,
                })
            else:
                numbers = raw_item.get('numbers')
                if not numbers or not isinstance(numbers, list):
                    raise ValueError(f'仓库为严进严出模式，items[{index}] 需要提供 numbers 数组')
                normalized_numbers = [
                    str(_to_positive_int(value, f'items[{index}].numbers[{pos}]'))
                    for pos, value in enumerate(numbers)
                ]
                num_of_rolls = len(normalized_numbers)
                quantity = sum(int(val) for val in normalized_numbers)
                quantity_of_rolls = ','.join(normalized_numbers)
                stock_flow_items.append({
                    'product_id': product.id,
                    'quantities': normalized_numbers,
                })

        normalized_items.append({
            'product': product,
            'price': price,
            'color': color,
            'quantity': quantity,
            'unit': unit,
            'empty_diff_percent': empty_diff_percent,
            'quantity_of_rolls': quantity_of_rolls,
            'num_of_rolls': num_of_rolls,
            'batch_number': batch_number,
            'remarks': remarks,
            'spec': spec,
            'order_quantity': order_quantity,
            'consume_detail_ids': consume_detail_ids_str,
            'printing_job': printing_job,
        })

    return normalized_items, stock_flow_items


def _approve_purchase_order(
    purchase_order: models.PurchaseOrder,
    reviewed_by,
) -> models.PurchaseOrder:
    with transaction.atomic():
        locked_order = models.PurchaseOrder.objects.select_related(
            'merchant', 'warehouse', 'supplier'
        ).prefetch_related('items').select_for_update().get(id=purchase_order.id)

        if locked_order.status == models.PurchaseOrderStatusEnum.APPROVED:
            return locked_order
        if locked_order.status == models.PurchaseOrderStatusEnum.CANCELLED:
            raise ValueError('作废状态的采购单无法再次审批')

        stock_flow_items = _build_stock_flow_items_from_order(locked_order)

        locked_order.status = models.PurchaseOrderStatusEnum.APPROVED
        locked_order.save(update_fields=['status', 'updated_at'])
        BalanceService.adjust_supplier_balance(
            merchant=locked_order.merchant,
            supplier=locked_order.supplier,
            delta=locked_order.get_total_amount(),
            source_type=models.BalanceChangeSourceEnum.PURCHASE_ORDER,
            source_id=locked_order.id,
        )

    created_by_id = getattr(reviewed_by, 'id', None)
    if _auto_stock_task_enabled(locked_order.merchant):
        logger.info('审批通过采购单 %s，触发入库任务', locked_order.id)
        create_purchase_order_stock_entries.delay(
            purchase_order_id=locked_order.id,
            warehouse_id=locked_order.warehouse_id,
            items=stock_flow_items,
            created_by_id=created_by_id,
        )

    locked_order.refresh_from_db(fields=['status', 'updated_at'])
    return locked_order


def _cancel_order_impl(
    *,
    order,
    order_model_cls,
    approved_status,
    cancelled_status,
    error_label: str,
    stock_source_type=None,
):
    """统一作废实现：禁止作废已审批单据，可选检查出入库记录。

    :param order: 待作废的单据实例（需提供 .id 用于加锁）
    :param order_model_cls: 单据模型类（用于 select_for_update）
    :param approved_status: 已审批状态枚举值
    :param cancelled_status: 作废状态枚举值
    :param error_label: 中文错误提示中的单据名称
    :param stock_source_type: StockChangeSourceEnum 值；为 None 则不检查出入库记录
    :returns: 刷新后的模型实例
    """
    with transaction.atomic():
        locked = order_model_cls.objects.select_for_update().get(id=order.id)
        if locked.status == approved_status:
            raise ValueError(f'已审批的{error_label}无法作废')
        if stock_source_type is not None and _order_has_stock_records(
            merchant_id=locked.merchant_id,
            source_type=stock_source_type,
            source_id=locked.id,
        ):
            raise ValueError(f'{error_label}已生成出入库记录，无法作废')
        locked.status = cancelled_status
        locked.save(update_fields=['status', 'updated_at'])
    locked.refresh_from_db(fields=['status', 'updated_at'])
    enqueue_business_order_speech(order=locked, order_label=error_label, action_label='作废')
    return locked


def _cancel_purchase_order(purchase_order: models.PurchaseOrder) -> models.PurchaseOrder:
    return _cancel_order_impl(
        order=purchase_order,
        order_model_cls=models.PurchaseOrder,
        approved_status=models.PurchaseOrderStatusEnum.APPROVED,
        cancelled_status=models.PurchaseOrderStatusEnum.CANCELLED,
        error_label='采购单',
        stock_source_type=stock_models.StockChangeSourceEnum.PURCHASE,
    )


def _approve_sales_order(
    sales_order: models.SalesOrder,
    reviewed_by,
) -> models.SalesOrder:
    with transaction.atomic():
        locked_order = models.SalesOrder.objects.select_related(
            'merchant', 'warehouse', 'customer'
        ).prefetch_related('items').select_for_update().get(id=sales_order.id)

        if locked_order.status == models.SalesOrderStatusEnum.APPROVED:
            return locked_order
        if locked_order.status == models.SalesOrderStatusEnum.CANCELLED:
            raise ValueError('作废状态的销售单无法再次审批')

        stock_flow_items = _build_stock_flow_items_from_order(locked_order)

        locked_order.status = models.SalesOrderStatusEnum.APPROVED
        locked_order.save(update_fields=['status', 'updated_at'])
        BalanceService.adjust_customer_balance(
            merchant=locked_order.merchant,
            customer=locked_order.customer,
            delta=locked_order.get_total_amount(),
            source_type=models.BalanceChangeSourceEnum.SALES_ORDER,
            source_id=locked_order.id,
        )

        consume_detail_ids: List[int] = []
        for item in locked_order.items.all():
            raw_ids = (item.consume_detail_ids or '').strip()
            if not raw_ids:
                continue
            consume_detail_ids.extend(
                int(value)
                for value in raw_ids.split(',')
                if value.strip()
            )
        if consume_detail_ids:
            stock_models.StockFreeze.objects.filter(
                merchant=locked_order.merchant,
                stock_detail_id__in=consume_detail_ids,
                status=stock_models.StockFreezeStatusEnum.FROZEN,
            ).update(
                status=stock_models.StockFreezeStatusEnum.COMPLETED,
                completed_by=reviewed_by,
                completed_at=timezone.now(),
                completed_with=locked_order.id,
            )

    created_by_id = getattr(reviewed_by, 'id', None)
    if _auto_stock_task_enabled(locked_order.merchant):
        logger.info('审批通过销售单 %s，触发出库任务', locked_order.id)
        create_sales_order_stock_entries.delay(
            sales_order_id=locked_order.id,
            warehouse_id=locked_order.warehouse_id,
            items=stock_flow_items,
            created_by_id=created_by_id,
        )

    locked_order.refresh_from_db(fields=['status', 'updated_at'])
    return locked_order


def _cancel_sales_order(sales_order: models.SalesOrder) -> models.SalesOrder:
    return _cancel_order_impl(
        order=sales_order,
        order_model_cls=models.SalesOrder,
        approved_status=models.SalesOrderStatusEnum.APPROVED,
        cancelled_status=models.SalesOrderStatusEnum.CANCELLED,
        error_label='销售单',
        stock_source_type=stock_models.StockChangeSourceEnum.SALES,
    )


def _approve_purchase_return_order(
    purchase_return_order: models.PurchaseReturnOrder,
    reviewed_by,
) -> models.PurchaseReturnOrder:
    with transaction.atomic():
        locked_order = models.PurchaseReturnOrder.objects.select_related(
            'merchant', 'warehouse', 'supplier'
        ).prefetch_related('items').select_for_update().get(id=purchase_return_order.id)

        if locked_order.status == models.PurchaseReturnStatusEnum.APPROVED:
            return locked_order
        if locked_order.status == models.PurchaseReturnStatusEnum.CANCELLED:
            raise ValueError('作废状态的采购退货单无法再次审批')

        stock_flow_items = _build_stock_flow_items_from_order(locked_order)

        locked_order.status = models.PurchaseReturnStatusEnum.APPROVED
        locked_order.save(update_fields=['status', 'updated_at'])
        BalanceService.adjust_supplier_balance(
            merchant=locked_order.merchant,
            supplier=locked_order.supplier,
            delta=-locked_order.get_total_amount(),
            source_type=models.BalanceChangeSourceEnum.PURCHASE_RETURN_ORDER,
            source_id=locked_order.id,
        )

    created_by_id = getattr(reviewed_by, 'id', None)
    if _auto_stock_task_enabled(locked_order.merchant):
        logger.info('审批通过采购退货单 %s，触发出库任务', locked_order.id)
        create_purchase_return_order_stock_entries.delay(
            purchase_return_order_id=locked_order.id,
            warehouse_id=locked_order.warehouse_id,
            items=stock_flow_items,
            created_by_id=created_by_id,
        )

    locked_order.refresh_from_db(fields=['status', 'updated_at'])
    return locked_order


def _cancel_purchase_return_order(
    purchase_return_order: models.PurchaseReturnOrder,
) -> models.PurchaseReturnOrder:
    return _cancel_order_impl(
        order=purchase_return_order,
        order_model_cls=models.PurchaseReturnOrder,
        approved_status=models.PurchaseReturnStatusEnum.APPROVED,
        cancelled_status=models.PurchaseReturnStatusEnum.CANCELLED,
        error_label='采购退货单',
        stock_source_type=stock_models.StockChangeSourceEnum.PURCHASE_RETURN,
    )


def _approve_sales_return_order(
    sales_return_order: models.SalesReturnOrder,
    reviewed_by,
) -> models.SalesReturnOrder:
    with transaction.atomic():
        locked_order = models.SalesReturnOrder.objects.select_related(
            'merchant', 'warehouse', 'customer'
        ).prefetch_related('items').select_for_update().get(id=sales_return_order.id)

        if locked_order.status == models.SalesReturnStatusEnum.APPROVED:
            return locked_order
        if locked_order.status == models.SalesReturnStatusEnum.CANCELLED:
            raise ValueError('作废状态的销售退货单无法再次审批')

        stock_flow_items = _build_stock_flow_items_from_order(locked_order)

        locked_order.status = models.SalesReturnStatusEnum.APPROVED
        locked_order.save(update_fields=['status', 'updated_at'])
        BalanceService.adjust_customer_balance(
            merchant=locked_order.merchant,
            customer=locked_order.customer,
            delta=-locked_order.get_total_amount(),
            source_type=models.BalanceChangeSourceEnum.SALES_RETURN_ORDER,
            source_id=locked_order.id,
        )

    created_by_id = getattr(reviewed_by, 'id', None)
    if _auto_stock_task_enabled(locked_order.merchant):
        logger.info('审批通过销售退货单 %s，触发入库任务', locked_order.id)
        create_sales_return_order_stock_entries.delay(
            sales_return_order_id=locked_order.id,
            warehouse_id=locked_order.warehouse_id,
            items=stock_flow_items,
            created_by_id=created_by_id,
        )

    locked_order.refresh_from_db(fields=['status', 'updated_at'])
    return locked_order


def _cancel_sales_return_order(
    sales_return_order: models.SalesReturnOrder,
) -> models.SalesReturnOrder:
    return _cancel_order_impl(
        order=sales_return_order,
        order_model_cls=models.SalesReturnOrder,
        approved_status=models.SalesReturnStatusEnum.APPROVED,
        cancelled_status=models.SalesReturnStatusEnum.CANCELLED,
        error_label='销售退货单',
        stock_source_type=stock_models.StockChangeSourceEnum.SALES_RETURN,
    )


def create_purchase_order_stock_entries_sync(
    *,
    purchase_order_id: int,
    warehouse_id: int,
    items: List[Dict[str, Any]],
    created_by_id: int | None = None,
) -> Dict[str, Any]:
    """
    根据采购单生成入库记录。
    """
    try:
        purchase_order = models.PurchaseOrder.objects.select_related('merchant').get(id=purchase_order_id)
    except models.PurchaseOrder.DoesNotExist:
        logger.error('PurchaseOrder %s 不存在，无法创建入库单', purchase_order_id)
        return {'error': 'purchase_order_not_found', 'purchase_order_id': purchase_order_id}

    merchant = purchase_order.merchant

    created_by = None
    if created_by_id:
        UserModel = get_user_model()
        created_by = UserModel.objects.filter(id=created_by_id).first()

    service = StockFlowService(merchant=merchant, created_by=created_by)
    record, details, created_count = service.stock_in(
        warehouse_id=warehouse_id,
        source_type=stock_models.StockChangeSourceEnum.PURCHASE,
        source_id=purchase_order.id,
        items=items,
    )

    payload = {
        'purchase_order_id': purchase_order.id,
        'stock_change_record_id': getattr(record, 'id', None),
        'created_details_count': created_count,
    }
    logger.info('采购单 %s 入库任务完成: %s', purchase_order.id, payload)
    return payload


def create_sales_order_stock_entries_sync(
    *,
    sales_order_id: int,
    warehouse_id: int,
    items: List[Dict[str, Any]],
    created_by_id: int | None = None,
) -> Dict[str, Any]:
    """
    根据销售单生成出库记录。
    """
    try:
        sales_order = models.SalesOrder.objects.select_related('merchant').get(id=sales_order_id)
    except models.SalesOrder.DoesNotExist:
        logger.error('SalesOrder %s 不存在，无法创建出库单', sales_order_id)
        return {'error': 'sales_order_not_found', 'sales_order_id': sales_order_id}

    merchant = sales_order.merchant

    created_by = None
    if created_by_id:
        UserModel = get_user_model()
        created_by = UserModel.objects.filter(id=created_by_id).first()

    service = StockFlowService(merchant=merchant, created_by=created_by)
    record, details, created_count = service.stock_out(
        warehouse_id=warehouse_id,
        source_type=stock_models.StockChangeSourceEnum.SALES,
        source_id=sales_order.id,
        items=items,
    )

    payload = {
        'sales_order_id': sales_order.id,
        'stock_change_record_id': getattr(record, 'id', None),
        'created_details_count': created_count,
    }
    logger.info('销售单 %s 出库任务完成: %s', sales_order.id, payload)
    return payload


def create_purchase_return_order_stock_entries_sync(
    *,
    purchase_return_order_id: int,
    warehouse_id: int,
    items: List[Dict[str, Any]],
    created_by_id: int | None = None,
) -> Dict[str, Any]:
    """
    根据采购退货单生成出库记录。
    """
    try:
        purchase_return_order = models.PurchaseReturnOrder.objects.select_related('merchant').get(
            id=purchase_return_order_id
        )
    except models.PurchaseReturnOrder.DoesNotExist:
        logger.error('PurchaseReturnOrder %s 不存在，无法创建出库单', purchase_return_order_id)
        return {'error': 'purchase_return_order_not_found', 'purchase_return_order_id': purchase_return_order_id}

    merchant = purchase_return_order.merchant

    created_by = None
    if created_by_id:
        UserModel = get_user_model()
        created_by = UserModel.objects.filter(id=created_by_id).first()

    service = StockFlowService(merchant=merchant, created_by=created_by)
    record, details, created_count = service.stock_out(
        warehouse_id=warehouse_id,
        source_type=stock_models.StockChangeSourceEnum.PURCHASE_RETURN,
        source_id=purchase_return_order.id,
        items=items,
    )

    payload = {
        'purchase_return_order_id': purchase_return_order.id,
        'stock_change_record_id': getattr(record, 'id', None),
        'created_details_count': created_count,
    }
    logger.info('采购退货单 %s 出库任务完成: %s', purchase_return_order.id, payload)
    return payload


def create_sales_return_order_stock_entries_sync(
    *,
    sales_return_order_id: int,
    warehouse_id: int,
    items: List[Dict[str, Any]],
    created_by_id: int | None = None,
) -> Dict[str, Any]:
    """
    根据销售退货单生成入库记录。
    """
    try:
        sales_return_order = models.SalesReturnOrder.objects.select_related('merchant').get(
            id=sales_return_order_id
        )
    except models.SalesReturnOrder.DoesNotExist:
        logger.error('SalesReturnOrder %s 不存在，无法创建入库单', sales_return_order_id)
        return {'error': 'sales_return_order_not_found', 'sales_return_order_id': sales_return_order_id}

    merchant = sales_return_order.merchant

    created_by = None
    if created_by_id:
        UserModel = get_user_model()
        created_by = UserModel.objects.filter(id=created_by_id).first()

    service = StockFlowService(merchant=merchant, created_by=created_by)
    record, details, created_count = service.stock_in(
        warehouse_id=warehouse_id,
        source_type=stock_models.StockChangeSourceEnum.SALES_RETURN,
        source_id=sales_return_order.id,
        items=items,
    )

    payload = {
        'sales_return_order_id': sales_return_order.id,
        'stock_change_record_id': getattr(record, 'id', None),
        'created_details_count': created_count,
    }
    logger.info('销售退货单 %s 入库任务完成: %s', sales_return_order.id, payload)
    return payload


def _build_stock_flow_items_from_order(order) -> List[Dict[str, Any]]:
    """
    根据订单明细还原 StockFlowService 所需的 items 结构。
    """
    warehouse_mode = order.warehouse.mode
    items_payload: List[Dict[str, Any]] = []

    order_items = order.items.all()
    if not order_items:
        raise ValueError('订单没有任何明细，无法生成库存记录')

    if warehouse_mode == basic_info_models.WareHouseModeEnum.UNRESTRICTED:
        for item in order_items:
            items_payload.append({
                'product_id': item.product_id,
                'value': str(item.quantity),
                'num_of_rolls': item.num_of_rolls or 1,
            })
        return items_payload

    # 严进 / 严进严出模式
    for item in order_items:
        consume_ids_raw = getattr(item, 'consume_detail_ids', None)
        if consume_ids_raw:
            normalized_ids = [value.strip() for value in consume_ids_raw.split(',') if value.strip()]
            if not normalized_ids:
                raise ValueError('严进严出订单缺少 consume_detail_ids 数据，无法生成库存记录')
            items_payload.append({
                'product_id': item.product_id,
                'consume_detail_ids': [int(value) for value in normalized_ids],
            })
            continue

        raw_numbers = (item.quantity_of_rolls or '').split(',')
        normalized_numbers = [value.strip() for value in raw_numbers if value.strip()]
        if not normalized_numbers:
            raise ValueError('严进仓订单缺少 numbers 数据，无法生成库存记录')
        items_payload.append({
            'product_id': item.product_id,
            'quantities': normalized_numbers,
        })
    return items_payload


def can_bind_source_id(record: stock_models.StockChangeRecord) -> bool:
    """
    判断库存记录是否允许绑定 source_id。
    目前默认允许，后续可按业务需要扩展。
    """
    return True


def bind_purchase_order_stock_change_record(
    *,
    purchase_order_id: int,
    stock_change_record_id: int,
    operator=None,
) -> stock_models.StockChangeRecord:
    purchase_order = models.PurchaseOrder.objects.select_related(
        'merchant', 'warehouse'
    ).get(id=purchase_order_id)

    if _auto_stock_task_enabled(purchase_order.merchant):
        raise ValueError('自动入库任务已开启，无法手动绑定库存记录')

    if _order_has_stock_records(
        merchant_id=purchase_order.merchant_id,
        source_type=stock_models.StockChangeSourceEnum.PURCHASE,
        source_id=purchase_order.id,
    ):
        raise ValueError('采购单已绑定库存记录')

    with transaction.atomic():
        record = stock_models.StockChangeRecord.objects.select_for_update().get(
            id=stock_change_record_id
        )

        if record.source_id is not None:
            raise ValueError('库存记录已绑定业务单据')
        if record.merchant_id != purchase_order.merchant_id:
            raise ValueError('库存记录不属于当前商户')
        if record.warehouse_id != purchase_order.warehouse_id:
            raise ValueError('库存记录仓库不匹配')
        if record.type != stock_models.StockChangeTypeEnum.ADD:
            raise ValueError('仅允许绑定入库类型库存记录')
        if record.source_type != stock_models.StockChangeSourceEnum.PURCHASE:
            raise ValueError('仅允许绑定采购来源的库存记录')
        if not can_bind_source_id(record):
            raise ValueError('库存记录当前状态不允许绑定')

        record.source_id = purchase_order.id
        record.save(update_fields=['source_id', 'updated_at'])

    operator_id = getattr(operator, 'id', None)
    logger.info(
        '采购单 %s 绑定库存记录 %s, operator=%s',
        purchase_order.id,
        record.id,
        operator_id,
    )
    return record


def _auto_stock_task_enabled(merchant: basic_info_models.Merchant) -> bool:
    try:
        setting = MerchantSettingService.get_setting(
            merchant,
            basic_info_models.MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS,
        )
    except basic_info_models.MerchantSetting.DoesNotExist:
        logger.warning('商户 %s 未配置 auto_create_stock_change_tasks，默认关闭', merchant.id)
        return False
    return setting.value is True


def _order_has_stock_records(
    *,
    merchant_id: int,
    source_type: stock_models.StockChangeSourceEnum,
    source_id: int,
) -> bool:
    return stock_models.StockChangeRecord.objects.filter(
        merchant_id=merchant_id,
        source_type=source_type,
        source_id=source_id,
    ).exists()


def _resolve_purchase_order_instance(
    purchase_order: models.PurchaseOrder | None,
    purchase_order_id: int | None,
) -> models.PurchaseOrder:
    if purchase_order is None and purchase_order_id is None:
        raise ValueError('必须提供 purchase_order 或 purchase_order_id')

    if purchase_order is not None:
        purchase_order_id = purchase_order.id

    return models.PurchaseOrder.objects.select_related('merchant', 'warehouse').prefetch_related('items').get(
        id=purchase_order_id
    )


def _resolve_sales_order_instance(
    sales_order: models.SalesOrder | None,
    sales_order_id: int | None,
) -> models.SalesOrder:
    if sales_order is None and sales_order_id is None:
        raise ValueError('必须提供 sales_order 或 sales_order_id')

    if sales_order is not None:
        sales_order_id = sales_order.id

    return models.SalesOrder.objects.select_related('merchant', 'warehouse').prefetch_related('items').get(
        id=sales_order_id
    )


def _resolve_purchase_return_order_instance(
    purchase_return_order: models.PurchaseReturnOrder | None,
    purchase_return_order_id: int | None,
) -> models.PurchaseReturnOrder:
    if purchase_return_order is None and purchase_return_order_id is None:
        raise ValueError('必须提供 purchase_return_order 或 purchase_return_order_id')

    if purchase_return_order is not None:
        purchase_return_order_id = purchase_return_order.id

    return models.PurchaseReturnOrder.objects.select_related('merchant', 'warehouse').prefetch_related('items').get(
        id=purchase_return_order_id
    )


def _resolve_sales_return_order_instance(
    sales_return_order: models.SalesReturnOrder | None,
    sales_return_order_id: int | None,
) -> models.SalesReturnOrder:
    if sales_return_order is None and sales_return_order_id is None:
        raise ValueError('必须提供 sales_return_order 或 sales_return_order_id')

    if sales_return_order is not None:
        sales_return_order_id = sales_return_order.id

    return models.SalesReturnOrder.objects.select_related('merchant', 'warehouse').prefetch_related('items').get(
        id=sales_return_order_id
    )


def _resolve_payment_order_instance(
    payment_order: models.PaymentOrder | None,
    payment_order_id: int | None,
) -> models.PaymentOrder:
    if payment_order is None and payment_order_id is None:
        raise ValueError('必须提供 payment_order 或 payment_order_id')
    if payment_order is not None:
        payment_order_id = payment_order.id
    return models.PaymentOrder.objects.select_related('merchant', 'supplier').get(id=payment_order_id)


def _resolve_receipt_order_instance(
    receipt_order: models.ReceiptOrder | None,
    receipt_order_id: int | None,
) -> models.ReceiptOrder:
    if receipt_order is None and receipt_order_id is None:
        raise ValueError('必须提供 receipt_order 或 receipt_order_id')
    if receipt_order is not None:
        receipt_order_id = receipt_order.id
    return models.ReceiptOrder.objects.select_related('merchant', 'customer').get(id=receipt_order_id)


def _to_decimal(value, field_name: str) -> Decimal:
    try:
        return Decimal(str(value))
    except (InvalidOperation, TypeError) as exc:
        raise ValueError(f'{field_name} 必须是合法数值') from exc


def _ensure_non_zero_amount(value, field_name: str) -> Decimal:
    amount = normalize_money_amount(_to_decimal(value, field_name), field_name=field_name)
    # 金额类字段要求非零（允许负数用于处理退款场景）
    # 付款单负金额 = 供应商退款，收款单负金额 = 退款给客户
    if amount == 0:
        raise ValueError(f'{field_name} 不能为 0')
    return amount


def _ensure_order_pending(order, pending_status, entity_name: str):
    if getattr(order, 'status', None) != pending_status:
        raise ValueError(f'{entity_name}当前状态不可修改')


def _ensure_non_negative_amount(value, field_name: str) -> Decimal:
    if value in (None, ''):
        return Decimal('0')
    amount = normalize_money_amount(_to_decimal(value, field_name), field_name=field_name)
    if amount < 0:
        raise ValueError(f'{field_name} 不能小于 0')
    return amount


def _to_positive_int(value, field_name: str) -> int:
    if value is None:
        raise ValueError(f'{field_name} 不能为空')
    decimal_value = _to_decimal(value, field_name)
    if decimal_value <= 0:
        raise ValueError(f'{field_name} 必须大于 0')
    if decimal_value != decimal_value.to_integral_value():
        raise ValueError(f'{field_name} 必须为整数')
    return int(decimal_value)


def _to_non_negative_int_or_none(value, field_name: str) -> int | None:
    if value in (None, ''):
        return None
    decimal_value = _to_decimal(value, field_name)
    if decimal_value < 0:
        raise ValueError(f'{field_name} 不能小于 0')
    if decimal_value != decimal_value.to_integral_value():
        raise ValueError(f'{field_name} 必须为整数')
    return int(decimal_value)


# ==================== Statement Builders ====================

_STATEMENT_TWO_PLACES = Decimal('0.01')
_STATEMENT_ZERO = Decimal('0')

STATEMENT_COUNTERPARTY_CHOICES = (
    ('customer', '客户'),
    ('supplier', '供应商'),
)

STATEMENT_ORDER_TYPE_CHOICES = (
    ('sales_order', '销售单'),
    ('sales_return_order', '销售退货单'),
    ('receipt_order', '收款单'),
    ('external_sales_order', '外部销售单'),
    ('external_sales_return_order', '外部销售退货单'),
    ('purchase_order', '采购单'),
    ('purchase_return_order', '采购退货单'),
    ('payment_order', '付款单'),
)


def build_customer_statement(
    *,
    merchant: basic_info_models.Merchant,
    customer: basic_info_models.Customer,
) -> Dict[str, Any]:
    """
    根据客户历史单据生成对账记录，供多个 API 复用。
    """
    statement_balance = BalanceService.get_customer_statement_balance(merchant=merchant, customer=customer)
    builder = _CustomerStatementBuilder(merchant=merchant, current_balance=statement_balance)
    records = builder.collect_records(customer)
    return builder.build_payload(
        counterparty_id=customer.id,
        counterparty_name=customer.name,
        records=records,
    )


def build_supplier_statement(
    *,
    merchant: basic_info_models.Merchant,
    supplier: basic_info_models.Supplier,
) -> Dict[str, Any]:
    """
    根据供应商历史单据生成对账记录，供多个 API 复用。
    """
    balance = BalanceService.get_supplier_balance(merchant=merchant, supplier=supplier)
    builder = _SupplierStatementBuilder(merchant=merchant, current_balance=balance)
    records = builder.collect_records(supplier)
    return builder.build_payload(
        counterparty_id=supplier.id,
        counterparty_name=supplier.name,
        records=records,
    )


def build_statement_summary(payload: Dict[str, Any]) -> Dict[str, str]:
    """
    通用汇总函数，配合 StatementResponseSerializer 的 summary 字段。
    """
    records = payload.get('records', []) or []
    total_positive = sum(
        (record.get('positive_amount', _STATEMENT_ZERO) for record in records),
        _STATEMENT_ZERO,
    )
    total_negative = sum(
        (record.get('negative_amount', _STATEMENT_ZERO) for record in records),
        _STATEMENT_ZERO,
    )
    return {
        'positive_total': _decimal_to_string(total_positive),
        'negative_total': _decimal_to_string(total_negative),
    }


class _StatementBuilder:
    def __init__(
        self,
        *,
        merchant: basic_info_models.Merchant,
        current_balance: Decimal,
    ):
        self.merchant = merchant
        self._current_balance_value = _normalize_statement_amount(current_balance)
        self._current_balance_display = _decimal_to_string(self._current_balance_value)

    def adjust_current_balance(self, delta: Decimal) -> None:
        self._current_balance_value += _normalize_statement_amount(delta)
        self._current_balance_display = _decimal_to_string(self._current_balance_value)

    def collect_records(self, counterparty) -> List[dict]:  # pragma: no cover - interface only
        raise NotImplementedError

    def build_payload(
        self,
        *,
        counterparty_id: int,
        counterparty_name: str,
        records: List[dict],
    ) -> Dict[str, Any]:
        sorted_records = self._sort_records(records)
        processed_records = self._attach_running_totals(sorted_records)
        return {
            'counterparty': counterparty_id,
            'counterparty_name': counterparty_name,
            'records': processed_records,
        }

    def _build_record(
        self,
        *,
        counterparty_id: int,
        counterparty_name: str,
        source_type: str,
        source_label: str,
        source_id: int,
        occurred_at,
        recorded_at,
        status: int,
        status_label: str,
        warehouse: basic_info_models.WareHouse | None = None,
        positive_amount,
        negative_amount,
        remarks: str | None = '',
        items: List[dict] | None = None,
        extra: dict | None = None,
        human_id: str | None = None,
    ) -> dict:
        items = items or []
        record = {
            'counterparty': counterparty_id,
            'counterparty_name': counterparty_name,
            'source_type': source_type,
            'source_label': source_label,
            'source_id': source_id,
            'human_id': str(human_id if human_id is not None else source_id),
            'occurred_at': occurred_at,
            'recorded_at': recorded_at,
            'status': status,
            'status_label': status_label,
            # 注意：收款单/付款单等资金类单据无仓库概念，warehouse 将为 None
            'warehouse': warehouse,
            'positive_amount': _normalize_statement_amount(positive_amount),
            'negative_amount': _normalize_statement_amount(negative_amount),
            'remarks': remarks or '',
            'items': items,
        }
        if extra:
            record['extra'] = extra
        return record

    def _aggregate_items(self, order_items) -> List[dict]:
        aggregated: OrderedDict[tuple, dict] = OrderedDict()
        for item in order_items:
            product = getattr(item, 'product', None)
            product_id = getattr(item, 'product_id', None)
            product_name = getattr(product, 'name', '')
            unit = getattr(item, 'unit', '')
            price = getattr(item, 'price', Decimal('0'))
            # 这些字段均来自订单明细（*OrderItem），用于在对账单中对 items 进行合理聚合：
            # - 同一产品不同颜色/规格应拆分展示，避免合并后信息丢失
            color = getattr(item, 'color', None) or ''
            spec = getattr(item, 'spec', None) or ''
            key = (product_id, product_name, unit, price, color, spec)
            if key not in aggregated:
                aggregated[key] = {
                    'product_id': product_id,
                    'product_name': product_name,
                    'quantity': Decimal('0'),
                    'price': price,
                    'unit': unit,
                    'color': color,
                    'spec': spec,
                    # 统一输出各条数数量（严格模式下为每条数量列表；非严格模式/未填则为空列表）
                    # 来源于各 *OrderItem 模型新增的 split_quantity_of_rolls() 方法
                    'quantity_of_rolls': [],
                    # 条数：来自各 *OrderItem.num_of_rolls。聚合时对相同产品/单价的多行明细累加。
                    'num_of_rolls': 0,
                }
            quantity_value = getattr(item, 'quantity', 0) or 0
            aggregated[key]['quantity'] += Decimal(str(quantity_value))

            num_of_rolls_value = getattr(item, 'num_of_rolls', None)
            try:
                num_of_rolls_int = int(num_of_rolls_value) if num_of_rolls_value is not None else 0
            except (TypeError, ValueError):  # pragma: no cover - defensive
                num_of_rolls_int = 0

            split_quantity_of_rolls = getattr(item, 'split_quantity_of_rolls', None)
            if callable(split_quantity_of_rolls):
                try:
                    rolls = split_quantity_of_rolls() or []
                except Exception:  # pragma: no cover - defensive
                    rolls = []
                if rolls:
                    aggregated[key]['quantity_of_rolls'].extend(list(rolls))
                    # 若无 num_of_rolls（或为 0），兜底使用 rolls 长度
                    if not num_of_rolls_int:
                        num_of_rolls_int = len(rolls)

            aggregated[key]['num_of_rolls'] += num_of_rolls_int
        return list(aggregated.values())

    def _sort_records(self, records: Iterable[dict]) -> List[dict]:
        return sorted(
            records,
            key=lambda item: (item['occurred_at'], item['recorded_at'], item['source_id']),
            reverse=True,
        )

    def _attach_running_totals(self, records: List[dict]) -> List[dict]:
        running_total = _STATEMENT_ZERO
        processed: List[dict] = []
        for record in records:
            record_copy = dict(record)
            record_copy['cumulative_amount'] = _decimal_to_string(running_total)
            record_copy['current_balance'] = self._current_balance_display
            arrears_amount = self._current_balance_value - running_total
            record_copy['arrears_amount'] = _decimal_to_string(arrears_amount)
            delta = record_copy['positive_amount'] - record_copy['negative_amount']
            running_total += delta
            processed.append(record_copy)
        return processed


class _CustomerStatementBuilder(_StatementBuilder):
    def __init__(self, *, merchant: basic_info_models.Merchant, current_balance: Decimal):
        super().__init__(merchant=merchant, current_balance=current_balance)
        self.external_balance_adjustment = Decimal('0')

    def collect_records(self, customer: basic_info_models.Customer) -> List[dict]:
        records: List[dict] = []
        records.extend(self._build_sales_records(customer))
        records.extend(self._build_sales_return_records(customer))
        records.extend(self._build_receipt_records(customer))
        records.extend(self._build_external_statement_records(customer))
        return records

    def _build_sales_records(self, customer: basic_info_models.Customer) -> List[dict]:
        qs = (
            models.SalesOrder.objects.filter(
                merchant=self.merchant,
                customer=customer,
                status=models.SalesOrderStatusEnum.APPROVED,
                is_red_flushed=False,
            )
            .select_related('customer', 'warehouse')
            .prefetch_related('items__product')
        )
        records = []
        for order in qs:
            items = self._aggregate_items(order.items.all())
            records.append(
                self._build_record(
                    counterparty_id=order.customer_id,
                    counterparty_name=order.customer.name,
                    source_type='sales_order',
                    source_label='销售单',
                    source_id=order.id,
                    human_id=order.human_id,
                    occurred_at=order.sales_date,
                    recorded_at=order.created_at,
                    status=order.status,
                    status_label=order.get_status_display(),
                    warehouse=order.warehouse,
                    positive_amount=order.get_total_amount(),
                    negative_amount=_STATEMENT_ZERO,
                    remarks=order.remarks,
                    items=items,
                )
            )
        return records

    def _build_sales_return_records(self, customer: basic_info_models.Customer) -> List[dict]:
        qs = (
            models.SalesReturnOrder.objects.filter(
                merchant=self.merchant,
                customer=customer,
                status=models.SalesReturnStatusEnum.APPROVED,
                is_red_flushed=False,
            )
            .select_related('customer', 'warehouse')
            .prefetch_related('items__product')
        )
        records = []
        for order in qs:
            items = self._aggregate_items(order.items.all())
            records.append(
                self._build_record(
                    counterparty_id=order.customer_id,
                    counterparty_name=order.customer.name,
                    source_type='sales_return_order',
                    source_label='销售退货单',
                    source_id=order.id,
                    human_id=order.human_id,
                    occurred_at=order.return_date,
                    recorded_at=order.created_at,
                    status=order.status,
                    status_label=order.get_status_display(),
                    warehouse=order.warehouse,
                    positive_amount=_STATEMENT_ZERO,
                    negative_amount=order.get_total_amount(),
                    remarks=order.remarks,
                    items=items,
                )
            )
        return records

    def _build_receipt_records(self, customer: basic_info_models.Customer) -> List[dict]:
        qs = (
            models.ReceiptOrder.objects.filter(
                merchant=self.merchant,
                customer=customer,
                status=models.ReceiptOrderStatusEnum.APPROVED,
                is_red_flushed=False,
            )
            .select_related('customer')
        )
        records = []
        for order in qs:
            # ERP 口径：收款单的 ZkJinE 从应收侧扣减（负的 positive_amount），
            # 已收只展示 FkJinE（order.amount）。净效果与 settlement_amount 一致。
            discount = order.discount_amount or Decimal('0')
            records.append(
                self._build_record(
                    counterparty_id=order.customer_id,
                    counterparty_name=order.customer.name,
                    source_type='receipt_order',
                    source_label='收款单',
                    source_id=order.id,
                    human_id=order.human_id,
                    occurred_at=order.receipt_date,
                    recorded_at=order.created_at,
                    status=order.status,
                    status_label=order.get_status_display(),
                    positive_amount=-discount,
                    negative_amount=order.amount,
                    remarks=order.remarks,
                )
            )
        return records

    def _build_external_statement_records(self, customer: basic_info_models.Customer) -> List[dict]:
        qs = models.ExternalCustomerStatementOrder.objects.filter(
            merchant=self.merchant,
            customer=customer,
        ).order_by('occurred_at', 'recorded_at', 'id')
        records = []
        for order in qs:
            sf = order.sf_amount or Decimal('0')
            zk = order.zk_amount or Decimal('0')
            if order.category == models.ExternalCustomerStatementCategoryEnum.SALE:
                source_type = 'external_sales_order'
                source_label = '外部销售单'
                # 净应收 = 毛额 - 折扣；现场收款作为 negative_amount 减少欠款
                positive_amount = order.total_amount - zk
                negative_amount = sf
            else:
                source_type = 'external_sales_return_order'
                source_label = '外部销售退货单'
                # 退货减少应收：退货金额 + 退货折扣都减少欠款
                positive_amount = _STATEMENT_ZERO
                negative_amount = order.total_amount + zk
            self.external_balance_adjustment += positive_amount - negative_amount
            records.append(
                self._build_record(
                    counterparty_id=order.customer_id,
                    counterparty_name=order.customer.name,
                    source_type=source_type,
                    source_label=source_label,
                    source_id=order.id,
                    human_id=str(order.id),
                    occurred_at=order.occurred_at,
                    recorded_at=order.recorded_at or order.created_at,
                    status=models.ReceiptOrderStatusEnum.APPROVED,
                    status_label='已同步',
                    positive_amount=positive_amount,
                    negative_amount=negative_amount,
                    remarks=order.remarks,
                    items=list(order.items_payload or []),
                    extra={
                        'external_source_id': order.external_source_id,
                        'external_customer_id': order.external_customer_id,
                        'settlement_method': order.settlement_method,
                    },
                )
            )
        return records


class _SupplierStatementBuilder(_StatementBuilder):
    def collect_records(self, supplier: basic_info_models.Supplier) -> List[dict]:
        records: List[dict] = []
        records.extend(self._build_purchase_records(supplier))
        records.extend(self._build_purchase_return_records(supplier))
        records.extend(self._build_payment_records(supplier))
        return records

    def _build_purchase_records(self, supplier: basic_info_models.Supplier) -> List[dict]:
        qs = (
            models.PurchaseOrder.objects.filter(
                merchant=self.merchant,
                supplier=supplier,
                status=models.PurchaseOrderStatusEnum.APPROVED,
                is_red_flushed=False,
            )
            .select_related('supplier', 'warehouse')
            .prefetch_related('items__product')
        )
        records = []
        for order in qs:
            items = self._aggregate_items(order.items.all())
            records.append(
                self._build_record(
                    counterparty_id=order.supplier_id,
                    counterparty_name=order.supplier.name,
                    source_type='purchase_order',
                    source_label='采购单',
                    source_id=order.id,
                    human_id=order.human_id,
                    occurred_at=order.purchase_date,
                    recorded_at=order.created_at,
                    status=order.status,
                    status_label=order.get_status_display(),
                    warehouse=order.warehouse,
                    positive_amount=order.get_total_amount(),
                    negative_amount=_STATEMENT_ZERO,
                    remarks=order.remarks,
                    items=items,
                )
            )
        return records

    def _build_purchase_return_records(self, supplier: basic_info_models.Supplier) -> List[dict]:
        qs = (
            models.PurchaseReturnOrder.objects.filter(
                merchant=self.merchant,
                supplier=supplier,
                status=models.PurchaseReturnStatusEnum.APPROVED,
                is_red_flushed=False,
            )
            .select_related('supplier', 'warehouse')
            .prefetch_related('items__product')
        )
        records = []
        for order in qs:
            items = self._aggregate_items(order.items.all())
            records.append(
                self._build_record(
                    counterparty_id=order.supplier_id,
                    counterparty_name=order.supplier.name,
                    source_type='purchase_return_order',
                    source_label='采购退货单',
                    source_id=order.id,
                    human_id=order.human_id,
                    occurred_at=order.return_date,
                    recorded_at=order.created_at,
                    status=order.status,
                    status_label=order.get_status_display(),
                    warehouse=order.warehouse,
                    positive_amount=_STATEMENT_ZERO,
                    negative_amount=order.get_total_amount(),
                    remarks=order.remarks,
                    items=items,
                )
            )
        return records

    def _build_payment_records(self, supplier: basic_info_models.Supplier) -> List[dict]:
        qs = (
            models.PaymentOrder.objects.filter(
                merchant=self.merchant,
                supplier=supplier,
                status=models.PaymentOrderStatusEnum.APPROVED,
                is_red_flushed=False,
            )
            .select_related('supplier')
        )
        records = []
        for order in qs:
            records.append(
                self._build_record(
                    counterparty_id=order.supplier_id,
                    counterparty_name=order.supplier.name,
                    source_type='payment_order',
                    source_label='付款单',
                    source_id=order.id,
                    human_id=order.human_id,
                    occurred_at=order.payment_date,
                    recorded_at=order.created_at,
                    status=order.status,
                    status_label=order.get_status_display(),
                    positive_amount=_STATEMENT_ZERO,
                    negative_amount=order.get_total_amount(),
                    remarks=order.remarks,
                )
            )
        return records


def _normalize_statement_amount(value) -> Decimal:
    if isinstance(value, Decimal):
        decimal_value = value
    else:
        decimal_value = Decimal(str(value))
    return decimal_value.quantize(_STATEMENT_TWO_PLACES, rounding=ROUND_HALF_UP)


def _decimal_to_string(value: Decimal) -> str:
    normalized = _normalize_statement_amount(value)
    return format(normalized, 'f')
