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:
@@ -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="删除成功")
|
||||
@@ -0,0 +1,68 @@
|
||||
"""客户路由。"""
|
||||
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.services.customer_service import CustomerService
|
||||
|
||||
router = APIRouter(prefix="/customers", tags=["客户"])
|
||||
|
||||
|
||||
class CustomerBody(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=128)
|
||||
phone: str = ""
|
||||
address: str = ""
|
||||
note: str = ""
|
||||
salesmen: str | None = None
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_customer(body: CustomerBody, user: CurrentUser, db: DbSession):
|
||||
"""新增客户。"""
|
||||
customer = await CustomerService(db).create(user, body.model_dump())
|
||||
return ok(data={"id": customer.id, "name": customer.name}, message="创建成功")
|
||||
|
||||
|
||||
@router.post("/search")
|
||||
async def search_customers(user: CurrentUser, db: DbSession,
|
||||
page: int = 1, page_size: int = 15, keyword: str = ""):
|
||||
"""客户搜索。"""
|
||||
items, total = await CustomerService(db).repo.search(user.company_id, page, page_size, keyword)
|
||||
return ok(data=paginated([{
|
||||
"id": c.id, "name": c.name, "phone": c.phone, "address": c.address,
|
||||
"note": c.note, "salesmen": c.salesmen, "balance": float(c.balance),
|
||||
"created_at": str(c.created_at),
|
||||
} for c in items], page, page_size, total))
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_customers(user: CurrentUser, db: DbSession, page: int = 1,
|
||||
page_size: int = 15, keyword: str = ""):
|
||||
"""客户分页列表。"""
|
||||
items, total = await CustomerService(db).repo.search(user.company_id, page, page_size, keyword)
|
||||
return ok(data=paginated([{
|
||||
"id": c.id, "name": c.name, "phone": c.phone, "address": c.address,
|
||||
"note": c.note, "salesmen": c.salesmen, "balance": float(c.balance),
|
||||
"created_at": str(c.created_at),
|
||||
} for c in items], page, page_size, total))
|
||||
|
||||
|
||||
@router.put("/{customer_id}")
|
||||
async def update_customer(customer_id: int, body: CustomerBody, user: CurrentUser, db: DbSession):
|
||||
"""编辑客户。"""
|
||||
try:
|
||||
c = await CustomerService(db).update(user, customer_id, body.model_dump())
|
||||
except KeyError as e:
|
||||
return fail(str(e))
|
||||
return ok(data={"id": c.id}, message="更新成功")
|
||||
|
||||
|
||||
@router.delete("/{customer_id}")
|
||||
async def delete_customer(customer_id: int, user: CurrentUser, db: DbSession):
|
||||
"""删除客户。"""
|
||||
try:
|
||||
await CustomerService(db).delete(user, customer_id)
|
||||
except KeyError as e:
|
||||
return fail(str(e))
|
||||
return ok(message="删除成功")
|
||||
@@ -0,0 +1,28 @@
|
||||
"""优化下料路由:一维型材切割方案计算(无状态,纯计算)。"""
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from huamenchuang.core.deps import CurrentUser
|
||||
from huamenchuang.core.response import fail, ok
|
||||
from huamenchuang.services.optimize_service import BarDemand, StockBar, optimize_1d
|
||||
|
||||
router = APIRouter(prefix="/optimize1d", tags=["优化下料"])
|
||||
|
||||
|
||||
class OptimizeRequest(BaseModel):
|
||||
demands: list[BarDemand] = Field(description="型材需求 [{length,qty,label}]")
|
||||
stocks: list[StockBar] = Field(description="库存原料 [{length,qty,label}]")
|
||||
kerf: int = Field(default=5, ge=0, le=50, description="锯口损耗(mm)")
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def optimize(body: OptimizeRequest, user: CurrentUser):
|
||||
"""一维优化下料(FFD 首递减算法)。"""
|
||||
if not body.demands or not body.stocks:
|
||||
return fail("需求和库存均不能为空")
|
||||
total_demand = sum(d.length * d.qty for d in body.demands)
|
||||
total_stock = sum(s.length * s.qty for s in body.stocks)
|
||||
if total_demand > total_stock:
|
||||
return fail(f"库存不足: 需求 {total_demand}mm > 库存 {total_stock}mm")
|
||||
result = optimize_1d(body.demands, body.stocks, body.kerf)
|
||||
return ok(data=result, message="优化完成")
|
||||
@@ -1,9 +1,12 @@
|
||||
"""路由汇总。"""
|
||||
from fastapi import APIRouter
|
||||
|
||||
from huamenchuang.api import auth, orders, windoor
|
||||
from huamenchuang.api import auth, contracts, customers, optimize, orders, windoor
|
||||
|
||||
api_router = APIRouter(prefix="/api")
|
||||
api_router.include_router(auth.router)
|
||||
api_router.include_router(orders.router)
|
||||
api_router.include_router(customers.router)
|
||||
api_router.include_router(contracts.router)
|
||||
api_router.include_router(windoor.router)
|
||||
api_router.include_router(optimize.router)
|
||||
|
||||
Reference in New Issue
Block a user