feat: 前后端全量开发 v0.2

后端:
- 客户模块 CRUD+搜索 (api/services/repositories 分层)
- 合同模块: FG单号规则/意向单转正式单/订单创建自动快照客户信息
- 优化下料: FFD首递减装箱算法(利用率统计/余料/未排入清单)
前端 (Vue3+TS+Element Plus):
- 7页面: 登录/工作台/订单中心/合同/客户/样板库/优化下料
- 自研二维窗型画布渲染器: 解析drawing_data渲染玻璃区域/外框/
  扇开向标记/尺寸标注, 支持缩放平移复位
- 样板库与订单联动(用此样板下单自动带图纸)
- axios统一envelope拦截/登录守卫/付费墙提示
工程:
- SPA history 回退路由, 构建产物挂载后端
- 全接口回归 6/6 通过
This commit is contained in:
HHY
2026-09-20 08:46:04 -07:00
parent d3e6de585c
commit f7a555dd6c
35 changed files with 3702 additions and 10 deletions
+85
View File
@@ -0,0 +1,85 @@
"""合同路由:意向单/正式单管理。"""
from fastapi import APIRouter
from pydantic import BaseModel, Field
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)
return ok(data={"contracts": paginated(
[_to_out(c) for c in items], 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="删除成功")