"""合同路由:意向单/正式单管理。""" from fastapi import APIRouter from pydantic import BaseModel, Field from sqlalchemy import select from huamenchuang.core.deps import CurrentUser, DbSession from huamenchuang.core.response import fail, ok, paginated from huamenchuang.repositories.contract_repo import status_label from huamenchuang.services.contract_service import ContractService router = APIRouter(prefix="/contracts", tags=["合同"]) class ContractBody(BaseModel): customer_id: int | None = None customer_name: str = "" phone: str = "" address: str = "" area: float = 0 count: int = 0 money: float = 0 note: str = "" class ConvertBody(BaseModel): contract_id: int def _to_out(c) -> dict: return { "id": c.id, "contract_no": c.contract_no, "customer_id": c.customer_id, "customer_name": c.customer_name, "phone": c.phone, "address": c.address, "area": float(c.area), "count": c.count, "money": float(c.money), "status": c.status, "status_label": status_label(c.status), "sign_status": c.sign_status, "note": c.note, "created_at": str(c.created_at), "updated_at": str(c.updated_at), } @router.post("") async def create_contract(body: ContractBody, user: CurrentUser, db: DbSession): """创建合同(默认意向单)。""" contract = await ContractService(db).create(user, body.model_dump()) return ok(data=_to_out(contract), message="创建成功") @router.get("") async def list_contracts(user: CurrentUser, db: DbSession, page: int = 1, page_size: int = 15, keyword: str = "", status: int | None = None): """合同分页列表(结构对齐目标站 {"contracts": {...}},含已收金额)。""" items, total = await ContractService(db).repo.search( user.company_id, page, page_size, keyword, status) # 当页合同的有效收款聚合(一次 GROUP BY,避免逐行查询) received_map: dict[int, float] = {} if items: from sqlalchemy import func from huamenchuang.models.business import ContractReceipt ids = [c.id for c in items] rows = (await db.execute( select(ContractReceipt.contract_id, func.sum(ContractReceipt.amount)) .where(ContractReceipt.contract_id.in_(ids), ContractReceipt.status == 1) .group_by(ContractReceipt.contract_id))).all() received_map = {cid: float(v or 0) for cid, v in rows} outs = [] for c in items: out = _to_out(c) out["received"] = round(received_map.get(c.id, 0), 2) out["unreceived"] = round(float(c.money) - out["received"], 2) outs.append(out) return ok(data={"contracts": paginated(outs, page, page_size, total)}) @router.put("/{contract_id}") async def update_contract(contract_id: int, body: ContractBody, user: CurrentUser, db: DbSession): """编辑合同。""" try: c = await ContractService(db).update(user, contract_id, body.model_dump()) except KeyError as e: return fail(str(e)) return ok(data=_to_out(c), message="更新成功") @router.post("/convert") async def convert_contract(body: ConvertBody, user: CurrentUser, db: DbSession): """意向单转正式单(对齐目标站 convert-contract/)。""" try: c = await ContractService(db).convert_to_formal(user, body.contract_id) except KeyError as e: return fail(str(e)) except ValueError as e: return fail(str(e)) return ok(data=_to_out(c), message="已转为正式单") @router.delete("/{contract_id}") async def delete_contract(contract_id: int, user: CurrentUser, db: DbSession): """删除合同。""" try: await ContractService(db).delete(user, contract_id) except KeyError as e: return fail(str(e)) return ok(message="删除成功")