"""资金管理API — 资金缺口预测 + 收付款计划 + 预警 + 应收催收闭环 (资金管理智能体)"""
import json
import logging
from datetime import datetime, timedelta
from fastapi import APIRouter, HTTPException, Depends, Query, Request
from sqlalchemy.orm import Session
from sqlalchemy import or_
from app.database import get_db
from app.deps import get_entity_id, resolve_entity_for_request
from app.auth_middleware import require_role
from app.models import CashPlan
from app.utils.cash_forecast_engine import (
    forecast_cash_flow_with_plans,
    check_cash_alerts,
    DEFAULT_CASH_WARNING,
    DEFAULT_CASH_CRITICAL,
)

logger = logging.getLogger("cma.cash")

router = APIRouter(
    prefix="/api/cma/cash",
    tags=["资金管理"],
    dependencies=[Depends(require_role("ceo", "finance", "business", "it"))],
)


def _aging_bucket(days_late: int) -> str:
    """账龄区间（按逾期天数）：未到期 / 0-30天 / 30-60天 / 60-90天 / 90天以上"""
    if days_late <= 0:
        return "未到期"
    if days_late <= 30:
        return "0-30天"
    if days_late <= 60:
        return "30-60天"
    if days_late <= 90:
        return "60-90天"
    return "90天以上"


def _plan_dict(p: CashPlan) -> dict:
    today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    due = p.plan_date.replace(hour=0, minute=0, second=0, microsecond=0) if p.plan_date else today
    days_late = (today - due).days
    paid = round(p.paid_amount or 0, 2)
    balance = round((p.amount or 0) - paid, 2)
    if p.plan_type == "receive":
        overdue = p.status == "pending" and due < today
        if p.status == "pending":
            aging = _aging_bucket(days_late)
        elif p.status == "completed":
            aging = "已结清"
        else:
            aging = "已取消"
    else:
        overdue = False
        aging = "—"
    return {
        "id": p.id,
        "entity_id": p.entity_id,
        "plan_type": p.plan_type,
        "plan_type_label": "收款" if p.plan_type == "receive" else "付款",
        "amount": round(p.amount or 0, 2),
        "paid_amount": paid,
        "receivable_balance": max(balance, 0),
        "plan_date": p.plan_date.strftime("%Y-%m-%d") if p.plan_date else "",
        "counterparty": p.counterparty or "",
        "description": p.description or "",
        "status": p.status,
        "status_label": {"pending": "待执行", "completed": "已完成", "cancelled": "已取消"}.get(p.status, p.status),
        "owner": p.owner or "",
        "source": p.source or "manual",
        "overdue": overdue,
        "overdue_days": max(days_late, 0) if overdue else 0,
        "aging_bucket": aging,
        "completed_at": p.completed_at.strftime("%Y-%m-%d %H:%M") if p.completed_at else None,
        "created_at": p.created_at.strftime("%Y-%m-%d %H:%M") if p.created_at else None,
    }


# ══════════════════════════════════════════════════════════
# 1. 资金缺口预测
# ══════════════════════════════════════════════════════════

@router.get("/gap-forecast")
def api_gap_forecast(
    entity_id: int = Depends(get_entity_id),
    days: int = Query(30, ge=1, le=90, description="预测天数"),
    current_cash: float = Query(None, description="当前现金余额(万元)，不传则自动获取"),
    db: Session = Depends(get_db),
):
    """资金缺口预测 — 趋势引擎 + 收付款计划，识别余额<警戒线的缺口日期"""
    try:
        return forecast_cash_flow_with_plans(entity_id, db, days=days, current_cash=current_cash)
    except Exception as e:
        logger.error(f"资金缺口预测失败: {e}", exc_info=True)
        raise HTTPException(400, f"资金缺口预测失败: {str(e)}")


@router.get("/balance")
def api_get_balance(
    entity_id: int = Depends(get_entity_id),
    db: Session = Depends(get_db),
):
    """获取当前现金余额（预测基线）"""
    from app.utils.cash_forecast_engine import get_current_cash_balance
    value = get_current_cash_balance(db, entity_id)
    return {"entity_id": entity_id, "current_cash": value}


@router.post("/balance")
def api_set_balance(
    data: dict,
    db: Session = Depends(get_db),
):
    """设置当前现金余额（万元），作为预测基线"""
    from app.utils.cash_forecast_engine import set_current_cash_balance
    value = float(data.get("current_cash", 0))
    if value < 0:
        raise HTTPException(400, "现金余额不能为负")
    set_current_cash_balance(db, value)
    return {"message": "当前现金余额已更新", "current_cash": value}


# ══════════════════════════════════════════════════════════
# 2. 收付款计划 CRUD
# ══════════════════════════════════════════════════════════

@router.get("/plans")
def api_list_plans(
    entity_id: int = Depends(get_entity_id),
    plan_type: str = Query(None, description="receive/pay"),
    status: str = Query(None, description="pending/completed/cancelled"),
    month: str = Query(None, description="YYYY-MM 按计划月份过滤"),
    page: int = Query(1, ge=1),
    page_size: int = Query(100, ge=1, le=500),
    db: Session = Depends(get_db),
):
    """收付款计划列表"""
    query = db.query(CashPlan).filter(CashPlan.entity_id == entity_id)
    if plan_type:
        query = query.filter(CashPlan.plan_type == plan_type)
    if status:
        query = query.filter(CashPlan.status == status)
    if month:
        try:
            y, m = int(month[:4]), int(month[5:7])
            start = datetime(y, m, 1)
            end = (start + timedelta(days=32)).replace(day=1)
            query = query.filter(CashPlan.plan_date >= start, CashPlan.plan_date < end)
        except Exception:
            raise HTTPException(400, "month格式应为YYYY-MM")
    total = query.count()
    plans = query.order_by(CashPlan.plan_date.asc(), CashPlan.id.desc()) \
        .offset((page - 1) * page_size).limit(page_size).all()
    return {"total": total, "data": [_plan_dict(p) for p in plans]}


@router.post("/plans")
def api_create_plan(request: Request, data: dict, db: Session = Depends(get_db)):
    """新建收付款计划"""
    plan_type = data.get("plan_type")
    if plan_type not in ("receive", "pay"):
        raise HTTPException(400, "plan_type必须为receive(收)或pay(付)")
    amount = float(data.get("amount", 0))
    if amount <= 0:
        raise HTTPException(400, "金额必须大于0")
    date_str = data.get("plan_date")
    if not date_str:
        raise HTTPException(400, "缺少计划日期")
    try:
        plan_date = datetime.strptime(str(date_str)[:10], "%Y-%m-%d")
    except Exception:
        raise HTTPException(400, "plan_date格式应为YYYY-MM-DD")

    plan = CashPlan(
        entity_id=resolve_entity_for_request(request, int(data.get("entity_id", 1))),
        plan_type=plan_type,
        amount=amount,
        plan_date=plan_date,
        counterparty=(data.get("counterparty") or "").strip(),
        description=(data.get("description") or "").strip(),
        status=data.get("status", "pending"),
        owner=(data.get("owner") or "").strip() or None,
        source=(data.get("source") or "manual").strip(),
        paid_amount=float(data.get("paid_amount") or 0),
    )
    db.add(plan)
    db.commit()
    db.refresh(plan)
    logger.info(f"新建收付款计划 #{plan.id} [{plan_type}] {amount}万 {date_str}")
    return {"message": "收付款计划已创建", "data": _plan_dict(plan)}


@router.put("/plans/{plan_id}")
def api_update_plan(plan_id: int, data: dict, db: Session = Depends(get_db)):
    """更新收付款计划"""
    plan = db.query(CashPlan).filter(CashPlan.id == plan_id).first()
    if not plan:
        raise HTTPException(404, "计划不存在")
    if "plan_type" in data:
        if data["plan_type"] not in ("receive", "pay"):
            raise HTTPException(400, "plan_type必须为receive或pay")
        plan.plan_type = data["plan_type"]
    if "amount" in data:
        amount = float(data["amount"])
        if amount <= 0:
            raise HTTPException(400, "金额必须大于0")
        plan.amount = amount
    if "plan_date" in data and data["plan_date"]:
        try:
            plan.plan_date = datetime.strptime(str(data["plan_date"])[:10], "%Y-%m-%d")
        except Exception:
            raise HTTPException(400, "plan_date格式应为YYYY-MM-DD")
    if "counterparty" in data:
        plan.counterparty = (data["counterparty"] or "").strip()
    if "description" in data:
        plan.description = (data["description"] or "").strip()
    if "owner" in data:
        plan.owner = (data["owner"] or "").strip() or None
    if "source" in data:
        plan.source = (data["source"] or "manual").strip()
    if "paid_amount" in data:
        plan.paid_amount = float(data["paid_amount"] or 0)
    if "status" in data:
        plan.status = data["status"]
        if data["status"] == "completed" and not plan.completed_at:
            plan.completed_at = datetime.now()
        elif data["status"] in ("pending", "cancelled"):
            plan.completed_at = None
    db.commit()
    db.refresh(plan)
    # 完成收款时联动：消除到期未收款预警 + 催收行动 + F_AR_DAYS
    if plan.plan_type == "receive" and plan.status == "completed":
        try:
            _resolve_plan_alerts(db, plan.entity_id, plan.id, plan.paid_amount or plan.amount)
            _sync_collection_action_plan(db, plan.entity_id)
            _update_ar_days_kpi(db, plan.entity_id)
            db.commit()
        except Exception as e:
            logger.warning(f"完成收款联动失败: {e}")
    return {"message": "计划已更新", "data": _plan_dict(plan)}


@router.delete("/plans/{plan_id}")
def api_delete_plan(plan_id: int, db: Session = Depends(get_db)):
    """删除收付款计划"""
    plan = db.query(CashPlan).filter(CashPlan.id == plan_id).first()
    if not plan:
        raise HTTPException(404, "计划不存在")
    db.delete(plan)
    db.commit()
    return {"message": "计划已删除"}


@router.post("/plans/{plan_id}/complete")
def api_complete_plan(plan_id: int, db: Session = Depends(get_db)):
    """标记计划为已完成（收款到账/付款完成）"""
    plan = db.query(CashPlan).filter(CashPlan.id == plan_id).first()
    if not plan:
        raise HTTPException(404, "计划不存在")
    plan.status = "completed"
    plan.completed_at = datetime.now()
    if plan.plan_type == "receive" and not plan.paid_amount:
        plan.paid_amount = plan.amount
    db.commit()
    db.refresh(plan)
    if plan.plan_type == "receive":
        try:
            _resolve_plan_alerts(db, plan.entity_id, plan.id, plan.paid_amount or plan.amount)
            _sync_collection_action_plan(db, plan.entity_id)
            _update_ar_days_kpi(db, plan.entity_id)
            db.commit()
        except Exception as e:
            logger.warning(f"完成收款联动失败: {e}")
    return {"message": "已标记完成", "data": _plan_dict(plan)}


# ══════════════════════════════════════════════════════════
# 3. 到期提醒 + 页面看板
# ══════════════════════════════════════════════════════════

@router.get("/upcoming")
def api_upcoming(
    entity_id: int = Depends(get_entity_id),
    days: int = Query(7, ge=1, le=30),
    db: Session = Depends(get_db),
):
    """未来N天到期提醒 + 已逾期未收/未付"""
    today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    upcoming = db.query(CashPlan).filter(
        CashPlan.entity_id == entity_id,
        CashPlan.status == "pending",
        CashPlan.plan_date >= today,
        CashPlan.plan_date <= today + timedelta(days=days),
    ).order_by(CashPlan.plan_date.asc()).all()
    overdue = db.query(CashPlan).filter(
        CashPlan.entity_id == entity_id,
        CashPlan.status == "pending",
        CashPlan.plan_date < today,
    ).order_by(CashPlan.plan_date.asc()).all()
    return {
        "days": days,
        "upcoming": [_plan_dict(p) for p in upcoming],
        "overdue": [_plan_dict(p) for p in overdue],
        "overdue_receive_amount": round(sum(p.amount for p in overdue if p.plan_type == "receive"), 2),
        "overdue_pay_amount": round(sum(p.amount for p in overdue if p.plan_type == "pay"), 2),
    }


@router.get("/dashboard")
def api_cash_dashboard(
    entity_id: int = Depends(get_entity_id),
    month: str = Query(None, description="YYYY-MM 默认本月"),
    days: int = Query(30),
    current_cash: float = Query(None, description="当前现金余额(万元)"),
    db: Session = Depends(get_db),
):
    """收付款计划页面看板 — 日历汇总 + 预测 + 到期提醒"""
    today = datetime.now()
    if month:
        try:
            y, m = int(month[:4]), int(month[5:7])
        except Exception:
            raise HTTPException(400, "month格式应为YYYY-MM")
    else:
        y, m = today.year, today.month
    start = datetime(y, m, 1)
    end = (start + timedelta(days=32)).replace(day=1)

    plans = db.query(CashPlan).filter(
        CashPlan.entity_id == entity_id,
        CashPlan.status == "pending",
        CashPlan.plan_date >= start,
        CashPlan.plan_date < end,
    ).all()

    # 日历：按天汇总 应收/应付
    calendar = {}
    for p in plans:
        dkey = p.plan_date.strftime("%Y-%m-%d")
        cell = calendar.setdefault(dkey, {"receive": 0.0, "pay": 0.0, "items": []})
        if p.plan_type == "receive":
            cell["receive"] += p.amount
        else:
            cell["pay"] += p.amount
        cell["items"].append(_plan_dict(p))

    # 本月合计
    month_receive = round(sum(p.amount for p in plans if p.plan_type == "receive"), 2)
    month_pay = round(sum(p.amount for p in plans if p.plan_type == "pay"), 2)

    forecast = forecast_cash_flow_with_plans(entity_id, db, days=days, current_cash=current_cash)
    upcoming = api_upcoming(entity_id, 7, db)

    return {
        "entity_id": entity_id,
        "month": f"{y:04d}-{m:02d}",
        "month_receive": month_receive,
        "month_pay": month_pay,
        "month_net": round(month_receive - month_pay, 2),
        "calendar": calendar,
        "forecast": forecast,
        "upcoming": upcoming,
    }


# ══════════════════════════════════════════════════════════
# 4. 资金预警 — 缺口前3天预警 + 到期未收款提醒
# ══════════════════════════════════════════════════════════

@router.post("/check-alerts")
def api_check_cash_alerts(entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db)):
    """手动触发资金预警检查（写入预警中心kpi_alerts）"""
    try:
        return check_cash_alerts(db, entity_id=entity_id)
    except Exception as e:
        logger.error(f"资金预警检查失败: {e}", exc_info=True)
        raise HTTPException(400, f"资金预警检查失败: {str(e)}")


@router.get("/alerts/status")
def api_cash_alert_status(entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db)):
    """资金预警状态概览 — 当前缺口/逾期情况（不写库，只读）"""
    result = forecast_cash_flow_with_plans(entity_id, db, days=30)
    return {
        "entity_id": entity_id,
        "warning_line": result["warning_line"],
        "critical_line": result["critical_line"],
        "gap_dates": result["gap_dates"],
        "pre_alerts": result["pre_alerts"],
        "min_cash": result["min_cash"],
        "min_cash_date": result["min_cash_date"],
        "summary": result["summary"],
    }


# ══════════════════════════════════════════════════════════
# 5. 应收催收闭环 — 催收视图 / 回款登记 / 博海数据录入
#    唯一应收载体：cash_plans(plan_type=receive)
# ══════════════════════════════════════════════════════════

# 博海应收款汇总表（2026H1期末余额，按业务员）— 录入源数据
# 来源: 应收款汇总表(业务员+客户).xlsx / bohai_comprehensive_report.md
BOHAI_AR_DATA = [
    # (业务员, 期末余额元, 业务说明)
    ("董均国", 387676, "IT业务-重点催收"),
    ("蒋亚文", 529786, "IT业务-重点催收"),
    ("陈艳", 89949, "IT业务"),
    ("李亚玲", 92450, "IT业务"),
    ("李巧玲", 45006, "IT业务"),
    ("贾妮", 27820, "IT业务"),
    ("王平安", 31298, "IT业务"),
    ("任富海", 26978, "IT业务"),
    ("其他", 6559, "IT业务"),
    ("王婧", 1065000, "酣客酒类(独立核算)"),
]


def _resolve_plan_alerts(db: Session, entity_id: int, plan_id: int, amount: float) -> int:
    """回款登记后自动消除该计划的【到期未收款】预警（kpi_alerts.alert_type=cash_plan）"""
    from app.models import KPIAlert
    alerts = db.query(KPIAlert).filter(
        KPIAlert.alert_type == "cash_plan",
        KPIAlert.status.in_(["pending", "processing"]),
    ).all()
    resolved = 0
    for a in alerts:
        try:
            sug = json.loads(a.suggestion or "{}")
        except Exception:
            continue
        if sug.get("plan_id") == plan_id:
            a.status = "resolved"
            a.resolution = f"回款登记+{amount:.2f}万，系统自动消除"
            a.resolved_at = datetime.now()
            resolved += 1
    if resolved:
        db.commit()
    return resolved


def _sync_collection_action_plan(db: Session, entity_id: int):
    """逾期应收 → 『应收账款催收行动』联动：有逾期→in_progress，全部结清→completed"""
    from app.models import ActionPlan
    today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    plans = db.query(CashPlan).filter(
        CashPlan.entity_id == entity_id,
        CashPlan.plan_type == "receive",
    ).all()
    pending = [p for p in plans if p.status == "pending"]
    overdue = [p for p in pending if p.plan_date < today]
    total_amount = sum(p.amount or 0 for p in plans)
    paid_amount = sum(p.paid_amount or 0 for p in plans)
    progress = round(paid_amount / total_amount * 100) if total_amount > 0 else 0
    if overdue:
        new_status = "in_progress"
    elif not pending:
        new_status = "completed"
        progress = 100
    else:
        new_status = "in_progress"

    ap = db.query(ActionPlan).filter(
        ActionPlan.title.like("%催收%"),
        ActionPlan.status != "cancelled",
    ).order_by(ActionPlan.id.asc()).first()
    if not ap:
        return
    changed = False
    if ap.status != new_status:
        ap.status = new_status
        changed = True
    if ap.progress is None or progress > (ap.progress or 0):
        ap.progress = progress
        changed = True
    if changed:
        db.commit()
        logger.info(f"催收行动#{ap.id} 联动: status={new_status} progress={progress}%")


def _update_ar_days_kpi(db: Session, entity_id: int):
    """联动F_AR_DAYS（应收周转天数KPI）— 按当前应收余额重算当期值 = 应收余额/月营收×30"""
    from app.models import KPIDefinition, KPIValue
    kpi = db.query(KPIDefinition).filter(
        KPIDefinition.entity_id == entity_id,
        KPIDefinition.kpi_code == "F_AR_DAYS",
    ).first()
    if not kpi:
        return
    plans = db.query(CashPlan).filter(
        CashPlan.entity_id == entity_id,
        CashPlan.plan_type == "receive",
        CashPlan.status == "pending",
    ).all()
    ar_balance_wan = round(sum((p.amount or 0) - (p.paid_amount or 0) for p in plans), 2)
    period = datetime.now().strftime("%Y-%m")
    monthly_rev = 100.0  # 缺省月营收(万元)
    rev_kpi = db.query(KPIDefinition).filter(
        KPIDefinition.entity_id == entity_id,
        KPIDefinition.kpi_code == "F_REVENUE",
    ).first()
    if rev_kpi:
        val = db.query(KPIValue).filter(
            KPIValue.kpi_id == rev_kpi.id,
            KPIValue.period == period,
            KPIValue.actual_value.isnot(None),
        ).order_by(KPIValue.id.desc()).first()
        if val and val.actual_value:
            monthly_rev = float(val.actual_value)
    ar_days = round(ar_balance_wan / monthly_rev * 30, 1) if monthly_rev > 0 else None
    if ar_days is None:
        return
    remark = f"应收催收闭环联动: 应收余额{ar_balance_wan:.2f}万/月营收{monthly_rev:.1f}万×30天"
    existing = db.query(KPIValue).filter(
        KPIValue.kpi_id == kpi.id,
        KPIValue.period == period,
    ).order_by(KPIValue.id.desc()).first()
    if existing:
        existing.actual_value = ar_days
        existing.source_type = "cash_plan"
        existing.remark = remark
        existing.calculated_at = datetime.now()
    else:
        db.add(KPIValue(
            kpi_id=kpi.id,
            period=period,
            actual_value=ar_days,
            source_type="cash_plan",
            data_status="calculated",
            remark=remark,
        ))
    db.commit()
    logger.info(f"F_AR_DAYS联动更新: {period} = {ar_days}天 (应收{ar_balance_wan}万)")


@router.get("/receivables")
def api_receivables(
    entity_id: int = Depends(get_entity_id),
    owner: str = Query(None, description="按负责人/业务员筛选"),
    status: str = Query(None, description="pending/completed/cancelled/overdue"),
    aging: str = Query(None, description="账龄: 未到期/0-30天/30-60天/60-90天/90天以上/已结清"),
    keyword: str = Query(None, description="客户/说明关键字"),
    db: Session = Depends(get_db),
):
    """应收催收视图 — 应收余额 + 账龄(30/60/90) + 逾期状态 + 负责人筛选"""
    query = db.query(CashPlan).filter(
        CashPlan.entity_id == entity_id,
        CashPlan.plan_type == "receive",
    )
    if owner:
        query = query.filter(CashPlan.owner == owner)
    if keyword:
        kw = f"%{keyword}%"
        query = query.filter(or_(CashPlan.counterparty.like(kw), CashPlan.description.like(kw)))

    plans = query.order_by(CashPlan.plan_date.asc(), CashPlan.id.asc()).all()
    items = []
    total_amount = total_paid = 0.0
    pending_balance = 0.0
    overdue_count = 0
    overdue_amount = 0.0
    aging_amount = {}
    completed_amount = 0.0
    for p in plans:
        d = _plan_dict(p)
        if status:
            if status == "overdue":
                if not d["overdue"]:
                    continue
            elif d["status"] != status:
                continue
        if aging and d["aging_bucket"] != aging:
            continue
        items.append(d)
        total_amount += d["amount"]
        total_paid += d["paid_amount"]
        if d["status"] == "pending":
            pending_balance += d["receivable_balance"]
        if d["overdue"]:
            overdue_count += 1
            overdue_amount += d["receivable_balance"]
        if d["status"] == "pending":
            aging_amount[d["aging_bucket"]] = aging_amount.get(d["aging_bucket"], 0) + d["receivable_balance"]
        elif d["status"] == "completed":
            completed_amount += d["amount"]

    owners = [r[0] for r in db.query(CashPlan.owner).filter(
        CashPlan.entity_id == entity_id,
        CashPlan.plan_type == "receive",
        CashPlan.owner.isnot(None),
        CashPlan.owner != "",
    ).distinct().order_by(CashPlan.owner.asc()).all()]

    return {
        "entity_id": entity_id,
        "total": len(items),
        "owners": owners,
        "summary": {
            "total_amount": round(total_amount, 2),
            "total_paid": round(total_paid, 2),
            "total_balance": round(total_amount - total_paid, 2),
            "pending_balance": round(pending_balance, 2),
            "overdue_count": overdue_count,
            "overdue_amount": round(overdue_amount, 2),
            "completed_amount": round(completed_amount, 2),
            "aging": {k: round(v, 2) for k, v in sorted(aging_amount.items(), key=lambda x: x[0])},
        },
        "data": items,
    }


@router.post("/receivables/{plan_id}/payment")
def api_register_payment(plan_id: int, data: dict, db: Session = Depends(get_db)):
    """回款登记 — 更新计划状态 + 自动消除到期未收款预警 + 联动催收行动/F_AR_DAYS"""
    plan = db.query(CashPlan).filter(CashPlan.id == plan_id).first()
    if not plan:
        raise HTTPException(404, "应收计划不存在")
    if plan.plan_type != "receive":
        raise HTTPException(400, "仅应收(receive)计划支持回款登记")
    if plan.status == "cancelled":
        raise HTTPException(400, "已取消的计划不能登记回款")
    amount = float(data.get("amount", 0))
    if amount <= 0:
        raise HTTPException(400, "回款金额必须大于0")
    paid_date_str = str(data.get("paid_date") or "")[:10]
    if not paid_date_str:
        paid_date_str = datetime.now().strftime("%Y-%m-%d")
    try:
        paid_date = datetime.strptime(paid_date_str, "%Y-%m-%d")
    except Exception:
        raise HTTPException(400, "paid_date格式应为YYYY-MM-DD")

    balance = round((plan.amount or 0) - (plan.paid_amount or 0), 2)
    if amount > balance + 1e-9:
        raise HTTPException(400, f"回款金额{amount}万超过应收余额{balance}万")

    plan.paid_amount = round((plan.paid_amount or 0) + amount, 2)
    if plan.paid_amount >= (plan.amount or 0) - 1e-9:
        plan.paid_amount = plan.amount
        plan.status = "completed"
        plan.completed_at = paid_date
    else:
        plan.status = "pending"
        plan.completed_at = None
    db.commit()

    entity_id = plan.entity_id
    resolved = _resolve_plan_alerts(db, entity_id, plan.id, amount)
    _sync_collection_action_plan(db, entity_id)
    _update_ar_days_kpi(db, entity_id)
    db.commit()
    db.refresh(plan)
    logger.info(f"回款登记 #{plan.id} {plan.counterparty or ''} +{amount}万 → {plan.status}, 消除预警{resolved}条")
    return {
        "message": "回款登记成功",
        "resolved_alerts": resolved,
        "data": _plan_dict(plan),
    }


@router.post("/import/bohai-ar")
def api_import_bohai_ar(data: dict = None, entity_id: int = Depends(get_entity_id), db: Session = Depends(get_db)):
    """录入博海应收汇总表数据(¥2.3M, 按业务员)到cash_plans — 幂等，重复调用不重复导入"""
    if data is None:
        data = {}
    existing = db.query(CashPlan).filter(
        CashPlan.source == "bohai_ar",
        CashPlan.entity_id == entity_id,
    ).count()
    if existing and not data.get("force"):
        return {"message": "博海应收已录入，未重复导入", "existing": existing, "imported": 0}
    if existing:
        db.query(CashPlan).filter(
            CashPlan.source == "bohai_ar",
            CashPlan.entity_id == entity_id,
        ).delete()
        db.commit()
    plan_date = datetime(2026, 6, 30)  # 2026H1期末余额基准日
    imported = 0
    total_wan = 0.0
    for sp, yuan, note in BOHAI_AR_DATA:
        wan = round(yuan / 10000, 2)
        total_wan += wan
        db.add(CashPlan(
            entity_id=entity_id,
            plan_type="receive",
            amount=wan,
            plan_date=plan_date,
            counterparty=sp,
            description=f"博海应收款汇总表2026H1期末余额({note})",
            status="pending",
            owner=sp,
            source="bohai_ar",
            paid_amount=0,
        ))
        imported += 1
    db.commit()
    # 触发预警检查生成到期未收款预警 + 联动催收行动/F_AR_DAYS
    try:
        check_cash_alerts(db, entity_id=entity_id)
    except Exception as e:
        logger.warning(f"导入后预警检查失败: {e}")
    _sync_collection_action_plan(db, entity_id)
    _update_ar_days_kpi(db, entity_id)
    db.commit()
    return {
        "message": "博海应收已录入",
        "imported": imported,
        "total_amount_wan": round(total_wan, 2),
        "entity_id": entity_id,
    }
