103 lines
3.8 KiB
Python
103 lines
3.8 KiB
Python
"""合同路由:意向单/正式单管理。"""
|
|
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="删除成功")
|