init
This commit is contained in:
@@ -4,6 +4,7 @@ from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import selectinload
|
||||
from typing import Optional, List, Dict, Tuple
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from database.database import get_db_session
|
||||
from services.auth_service import get_current_active_user
|
||||
@@ -414,15 +415,15 @@ async def get_finance_summary(
|
||||
) or 0
|
||||
|
||||
return FinanceSummaryResponse(
|
||||
receivable_total=round(float(receivable_total), 2),
|
||||
payable_total=round(float(payable_total), 2),
|
||||
monthly_receipt_total=round(float(monthly_receipt_total), 2),
|
||||
monthly_payment_total=round(float(monthly_payment_total), 2),
|
||||
receivable_total=Decimal(str(receivable_total)),
|
||||
payable_total=Decimal(str(payable_total)),
|
||||
monthly_receipt_total=Decimal(str(monthly_receipt_total)),
|
||||
monthly_payment_total=Decimal(str(monthly_payment_total)),
|
||||
selected_year=selected_year,
|
||||
selected_quarter=selected_quarter,
|
||||
period_label=period_label,
|
||||
period_receipt_total=round(float(period_receipt_total), 2),
|
||||
period_payment_total=round(float(period_payment_total), 2),
|
||||
period_receipt_total=Decimal(str(period_receipt_total)),
|
||||
period_payment_total=Decimal(str(period_payment_total)),
|
||||
overdue_receivable_count=0,
|
||||
overdue_payable_count=0,
|
||||
)
|
||||
@@ -464,9 +465,9 @@ async def get_partner_statement(
|
||||
"outstanding_total": 0.0,
|
||||
},
|
||||
)
|
||||
total_amount = float(order.total_amount or 0)
|
||||
settled_amount = float(order.received_amount or 0)
|
||||
outstanding = max(total_amount - settled_amount, 0.0)
|
||||
total_amount = Decimal(str(order.total_amount or 0))
|
||||
settled_amount = Decimal(str(order.received_amount or 0))
|
||||
outstanding = max(total_amount - settled_amount, Decimal("0"))
|
||||
partner_stat["order_count"] += 1
|
||||
partner_stat["order_total"] += total_amount
|
||||
partner_stat["settled_total"] += settled_amount
|
||||
@@ -495,7 +496,7 @@ async def get_partner_statement(
|
||||
},
|
||||
)
|
||||
partner_stat["transaction_count"] += 1
|
||||
partner_stat["transaction_total"] += float(txn.amount or 0)
|
||||
partner_stat["transaction_total"] += Decimal(str(txn.amount or 0))
|
||||
else:
|
||||
order_rows = await db_session.execute(
|
||||
select(PurchaseOrder, Supplier)
|
||||
@@ -518,9 +519,9 @@ async def get_partner_statement(
|
||||
"outstanding_total": 0.0,
|
||||
},
|
||||
)
|
||||
total_amount = float(order.total_amount or 0)
|
||||
settled_amount = float(order.paid_amount or 0)
|
||||
outstanding = max(total_amount - settled_amount, 0.0)
|
||||
total_amount = Decimal(str(order.total_amount or 0))
|
||||
settled_amount = Decimal(str(order.paid_amount or 0))
|
||||
outstanding = max(total_amount - settled_amount, Decimal("0"))
|
||||
partner_stat["order_count"] += 1
|
||||
partner_stat["order_total"] += total_amount
|
||||
partner_stat["settled_total"] += settled_amount
|
||||
@@ -549,7 +550,7 @@ async def get_partner_statement(
|
||||
},
|
||||
)
|
||||
partner_stat["transaction_count"] += 1
|
||||
partner_stat["transaction_total"] += float(txn.amount or 0)
|
||||
partner_stat["transaction_total"] += Decimal(str(txn.amount or 0))
|
||||
|
||||
missing_partner_ids = [pid for pid, item in stats_map.items() if "#" in item["partner_name"]]
|
||||
if missing_partner_ids:
|
||||
@@ -572,10 +573,10 @@ async def get_partner_statement(
|
||||
partner_name=item["partner_name"],
|
||||
order_count=item["order_count"],
|
||||
transaction_count=item["transaction_count"],
|
||||
order_total=round(float(item["order_total"]), 2),
|
||||
settled_total=round(float(item["settled_total"]), 2),
|
||||
transaction_total=round(float(item["transaction_total"]), 2),
|
||||
outstanding_total=round(float(item["outstanding_total"]), 2),
|
||||
order_total=Decimal(str(item["order_total"])),
|
||||
settled_total=Decimal(str(item["settled_total"])),
|
||||
transaction_total=Decimal(str(item["transaction_total"])),
|
||||
outstanding_total=Decimal(str(item["outstanding_total"])),
|
||||
period_year=selected_year,
|
||||
period_quarter=selected_quarter,
|
||||
)
|
||||
@@ -587,10 +588,10 @@ async def get_partner_statement(
|
||||
year=selected_year,
|
||||
quarter=selected_quarter,
|
||||
period_label=period_label,
|
||||
order_total=round(float(sum(item.order_total for item in items)), 2),
|
||||
settled_total=round(float(sum(item.settled_total for item in items)), 2),
|
||||
transaction_total=round(float(sum(item.transaction_total for item in items)), 2),
|
||||
outstanding_total=round(float(sum(item.outstanding_total for item in items)), 2),
|
||||
order_total=Decimal(str(sum(item.order_total for item in items))),
|
||||
settled_total=Decimal(str(sum(item.settled_total for item in items))),
|
||||
transaction_total=Decimal(str(sum(item.transaction_total for item in items))),
|
||||
outstanding_total=Decimal(str(sum(item.outstanding_total for item in items))),
|
||||
items=items,
|
||||
)
|
||||
|
||||
@@ -642,15 +643,15 @@ async def get_partner_product_statement(
|
||||
},
|
||||
)
|
||||
|
||||
item_amount = float(item.amount or 0)
|
||||
order_total = float(order.total_amount or 0)
|
||||
order_settled = max(float(order.received_amount or 0), 0.0)
|
||||
ratio = (item_amount / order_total) if order_total > 1e-9 else 0.0
|
||||
item_amount = Decimal(str(item.amount or 0))
|
||||
order_total = Decimal(str(order.total_amount or 0))
|
||||
order_settled = max(Decimal(str(order.received_amount or 0)), Decimal("0"))
|
||||
ratio = (item_amount / order_total) if order_total > Decimal("1e-9") else Decimal("0")
|
||||
item_settled = min(item_amount, order_settled * ratio)
|
||||
item_outstanding = max(item_amount - item_settled, 0.0)
|
||||
item_outstanding = max(item_amount - item_settled, Decimal("0"))
|
||||
|
||||
stat["order_ids"].add(order.id)
|
||||
stat["order_quantity"] += float(item.quantity or 0)
|
||||
stat["order_quantity"] += Decimal(str(item.quantity or 0))
|
||||
stat["order_amount"] += item_amount
|
||||
stat["settled_amount"] += item_settled
|
||||
stat["outstanding_amount"] += item_outstanding
|
||||
@@ -686,15 +687,15 @@ async def get_partner_product_statement(
|
||||
},
|
||||
)
|
||||
|
||||
item_amount = float(item.amount or 0)
|
||||
order_total = float(order.total_amount or 0)
|
||||
order_settled = max(float(order.paid_amount or 0), 0.0)
|
||||
ratio = (item_amount / order_total) if order_total > 1e-9 else 0.0
|
||||
item_amount = Decimal(str(item.amount or 0))
|
||||
order_total = Decimal(str(order.total_amount or 0))
|
||||
order_settled = max(Decimal(str(order.paid_amount or 0)), Decimal("0"))
|
||||
ratio = (item_amount / order_total) if order_total > Decimal("1e-9") else Decimal("0")
|
||||
item_settled = min(item_amount, order_settled * ratio)
|
||||
item_outstanding = max(item_amount - item_settled, 0.0)
|
||||
item_outstanding = max(item_amount - item_settled, Decimal("0"))
|
||||
|
||||
stat["order_ids"].add(order.id)
|
||||
stat["order_quantity"] += float(item.quantity or 0)
|
||||
stat["order_quantity"] += Decimal(str(item.quantity or 0))
|
||||
stat["order_amount"] += item_amount
|
||||
stat["settled_amount"] += item_settled
|
||||
stat["outstanding_amount"] += item_outstanding
|
||||
@@ -707,10 +708,10 @@ async def get_partner_product_statement(
|
||||
product_sku=item["product_sku"],
|
||||
product_name=item["product_name"],
|
||||
order_count=len(item["order_ids"]),
|
||||
order_quantity=round(float(item["order_quantity"]), 2),
|
||||
order_amount=round(float(item["order_amount"]), 2),
|
||||
settled_amount=round(float(item["settled_amount"]), 2),
|
||||
outstanding_amount=round(float(item["outstanding_amount"]), 2),
|
||||
order_quantity=Decimal(str(item["order_quantity"])),
|
||||
order_amount=Decimal(str(item["order_amount"])),
|
||||
settled_amount=Decimal(str(item["settled_amount"])),
|
||||
outstanding_amount=Decimal(str(item["outstanding_amount"])),
|
||||
period_year=selected_year,
|
||||
period_quarter=selected_quarter,
|
||||
)
|
||||
@@ -727,9 +728,9 @@ async def get_partner_product_statement(
|
||||
quarter=selected_quarter,
|
||||
period_label=period_label,
|
||||
partner_id=partner_id,
|
||||
order_amount_total=round(float(sum(item.order_amount for item in items)), 2),
|
||||
settled_amount_total=round(float(sum(item.settled_amount for item in items)), 2),
|
||||
outstanding_amount_total=round(float(sum(item.outstanding_amount for item in items)), 2),
|
||||
order_amount_total=Decimal(str(sum(item.order_amount for item in items))),
|
||||
settled_amount_total=Decimal(str(sum(item.settled_amount for item in items))),
|
||||
outstanding_amount_total=Decimal(str(sum(item.outstanding_amount for item in items))),
|
||||
items=items,
|
||||
)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, or_, func, delete
|
||||
from typing import Optional, List, Dict
|
||||
from decimal import Decimal
|
||||
|
||||
from database.database import get_db_session
|
||||
from services.auth_service import get_current_active_user, get_current_admin_user
|
||||
@@ -45,10 +46,10 @@ async def _calculate_material_cost_map(db_session: AsyncSession, product_ids: Li
|
||||
.where(ProductMaterial.finished_product_id.in_(product_ids))
|
||||
.group_by(ProductMaterial.finished_product_id)
|
||||
)
|
||||
return {row[0]: float(row[1] or 0) for row in result.all()}
|
||||
return {row[0]: Decimal(str(row[1] or 0)) for row in result.all()}
|
||||
|
||||
|
||||
def _build_product_response(product: Product, material_cost: float = 0) -> ProductResponse:
|
||||
def _build_product_response(product: Product, material_cost: Decimal = Decimal("0")) -> ProductResponse:
|
||||
return ProductResponse(
|
||||
id=product.id,
|
||||
sku=product.sku,
|
||||
@@ -57,11 +58,11 @@ def _build_product_response(product: Product, material_cost: float = 0) -> Produ
|
||||
category=product.category,
|
||||
unit=product.unit,
|
||||
item_type=product.item_type,
|
||||
cost_price=float(product.cost_price or 0),
|
||||
sale_price=float(product.sale_price or 0),
|
||||
cost_price=Decimal(str(product.cost_price or 0)),
|
||||
sale_price=Decimal(str(product.sale_price or 0)),
|
||||
min_stock=product.min_stock,
|
||||
max_stock=product.max_stock,
|
||||
material_cost=round(float(material_cost), 4),
|
||||
material_cost=Decimal(str(material_cost)).quantize(Decimal("0.0001")),
|
||||
is_active=product.is_active,
|
||||
created_at=product.created_at,
|
||||
)
|
||||
@@ -184,25 +185,25 @@ async def get_product_bom(
|
||||
)
|
||||
|
||||
items: List[ProductMaterialItemResponse] = []
|
||||
total_material_cost = 0.0
|
||||
total_material_cost = Decimal("0")
|
||||
for bom, material in bom_result.all():
|
||||
line_cost = float(material.cost_price or 0) * float(bom.quantity)
|
||||
line_cost = Decimal(str(material.cost_price or 0)) * Decimal(str(bom.quantity))
|
||||
total_material_cost += line_cost
|
||||
items.append(
|
||||
ProductMaterialItemResponse(
|
||||
material_id=material.id,
|
||||
material_sku=material.sku,
|
||||
material_name=material.name,
|
||||
quantity=round(float(bom.quantity), 4),
|
||||
unit_cost=round(float(material.cost_price or 0), 4),
|
||||
line_cost=round(float(line_cost), 4),
|
||||
quantity=Decimal(str(bom.quantity)),
|
||||
unit_cost=Decimal(str(material.cost_price or 0)),
|
||||
line_cost=line_cost,
|
||||
)
|
||||
)
|
||||
|
||||
return ProductBOMResponse(
|
||||
product_id=product.id,
|
||||
product_name=product.name,
|
||||
total_material_cost=round(float(total_material_cost), 4),
|
||||
total_material_cost=total_material_cost,
|
||||
items=items,
|
||||
)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func
|
||||
from typing import Optional, List
|
||||
from decimal import Decimal
|
||||
|
||||
from database.database import get_db_session
|
||||
from services.auth_service import get_current_active_user
|
||||
@@ -167,8 +168,8 @@ async def _apply_order_items(
|
||||
order_id=order.id,
|
||||
product_id=item_data.product_id,
|
||||
quantity=int(item_data.quantity),
|
||||
unit_price=float(unit_price),
|
||||
amount=float(item_data.quantity) * float(unit_price),
|
||||
unit_price=Decimal(str(unit_price)),
|
||||
amount=Decimal(str(item_data.quantity)) * Decimal(str(unit_price)),
|
||||
remark=item_data.remark or None
|
||||
)
|
||||
db_session.add(item)
|
||||
@@ -408,7 +409,7 @@ async def receive_purchase_order(
|
||||
reference_id=order.id,
|
||||
reference_no=order.order_no,
|
||||
unit_price=item.unit_price,
|
||||
total_amount=round(float(item.unit_price * receive_item.receive_quantity), 4),
|
||||
total_amount=Decimal(str(item.unit_price * receive_item.receive_quantity)),
|
||||
remark=payload.remark or f"采购单{order.order_no}到货入库",
|
||||
operator_id=current_user.id
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ from sqlalchemy import select, func, delete, update
|
||||
from typing import Optional, List
|
||||
from math import ceil
|
||||
from pydantic import BaseModel, Field
|
||||
from decimal import Decimal
|
||||
|
||||
from database.database import get_db_session
|
||||
from services.auth_service import get_current_active_user
|
||||
@@ -58,10 +59,10 @@ def _build_sales_order_response(order: SalesOrder, customer_name: str) -> SalesO
|
||||
status=order.status,
|
||||
production_status=order.production_status or "not_started",
|
||||
production_no=order.production_no,
|
||||
planned_material_cost=round(float(order.planned_material_cost or 0), 4),
|
||||
actual_material_cost=round(float(order.actual_material_cost or 0), 4),
|
||||
total_amount=order.total_amount,
|
||||
received_amount=order.received_amount,
|
||||
planned_material_cost=Decimal(str(order.planned_material_cost or 0)),
|
||||
actual_material_cost=Decimal(str(order.actual_material_cost or 0)),
|
||||
total_amount=Decimal(str(order.total_amount or 0)),
|
||||
received_amount=Decimal(str(order.received_amount or 0)),
|
||||
remark=order.remark,
|
||||
created_at=order.created_at
|
||||
)
|
||||
@@ -89,10 +90,10 @@ async def _build_sales_order_detail_response(
|
||||
status=order.status,
|
||||
production_status=order.production_status or "not_started",
|
||||
production_no=order.production_no,
|
||||
planned_material_cost=round(float(order.planned_material_cost or 0), 4),
|
||||
actual_material_cost=round(float(order.actual_material_cost or 0), 4),
|
||||
total_amount=order.total_amount,
|
||||
received_amount=order.received_amount,
|
||||
planned_material_cost=Decimal(str(order.planned_material_cost or 0)),
|
||||
actual_material_cost=Decimal(str(order.actual_material_cost or 0)),
|
||||
total_amount=Decimal(str(order.total_amount or 0)),
|
||||
received_amount=Decimal(str(order.received_amount or 0)),
|
||||
remark=order.remark,
|
||||
created_at=order.created_at,
|
||||
items=[
|
||||
@@ -101,8 +102,8 @@ async def _build_sales_order_detail_response(
|
||||
product_id=item.product_id,
|
||||
quantity=item.quantity,
|
||||
delivered_quantity=item.delivered_quantity,
|
||||
unit_price=item.unit_price,
|
||||
amount=item.amount,
|
||||
unit_price=Decimal(str(item.unit_price or 0)),
|
||||
amount=Decimal(str(item.amount or 0)),
|
||||
remark=item.remark
|
||||
) for item in items
|
||||
]
|
||||
@@ -152,12 +153,12 @@ async def _build_material_plan(db_session: AsyncSession, order: SalesOrder) -> t
|
||||
for order_item in order_items:
|
||||
bom_items = bom_by_finished_id.get(int(order_item.product_id)) or []
|
||||
for bom, material in bom_items:
|
||||
qty = float(order_item.quantity) * float(bom.quantity or 0) * (1 + float(bom.loss_rate or 0))
|
||||
entry = required_qty_map.setdefault(material.id, {"material": material, "required_qty": 0.0})
|
||||
qty = Decimal(str(order_item.quantity)) * Decimal(str(bom.quantity or 0)) * (1 + Decimal(str(bom.loss_rate or 0)))
|
||||
entry = required_qty_map.setdefault(material.id, {"material": material, "required_qty": Decimal("0")})
|
||||
entry["required_qty"] += qty
|
||||
|
||||
if not required_qty_map:
|
||||
return [], 0
|
||||
return [], Decimal("0")
|
||||
|
||||
material_ids = list(required_qty_map.keys())
|
||||
stock_result = await db_session.execute(
|
||||
@@ -165,28 +166,28 @@ async def _build_material_plan(db_session: AsyncSession, order: SalesOrder) -> t
|
||||
.where(Inventory.product_id.in_(material_ids))
|
||||
.group_by(Inventory.product_id)
|
||||
)
|
||||
stock_map = {row[0]: int(row[1] or 0) for row in stock_result.all()}
|
||||
stock_map = {row[0]: Decimal(str(row[1] or 0)) for row in stock_result.all()}
|
||||
|
||||
plan_items = []
|
||||
planned_material_cost = 0.0
|
||||
planned_material_cost = Decimal("0")
|
||||
for material_id, entry in required_qty_map.items():
|
||||
material = entry["material"]
|
||||
required_qty = int(ceil(entry["required_qty"]))
|
||||
available_qty = stock_map.get(material_id, 0)
|
||||
shortage_qty = max(required_qty - available_qty, 0)
|
||||
unit_cost = float(material.cost_price or 0)
|
||||
required_cost = required_qty * unit_cost
|
||||
available_qty = stock_map.get(material_id, Decimal("0"))
|
||||
shortage_qty = max(required_qty - int(available_qty), 0)
|
||||
unit_cost = Decimal(str(material.cost_price or 0))
|
||||
required_cost = Decimal(str(required_qty)) * unit_cost
|
||||
planned_material_cost += required_cost
|
||||
plan_items.append(
|
||||
ProductionMaterialPlanItemResponse(
|
||||
material_id=material.id,
|
||||
material_sku=material.sku,
|
||||
material_name=material.name,
|
||||
required_quantity=required_qty,
|
||||
required_quantity=Decimal(str(required_qty)),
|
||||
available_quantity=available_qty,
|
||||
shortage_quantity=shortage_qty,
|
||||
unit_cost=round(unit_cost, 4),
|
||||
required_cost=round(required_cost, 4),
|
||||
shortage_quantity=Decimal(str(shortage_qty)),
|
||||
unit_cost=unit_cost,
|
||||
required_cost=required_cost,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -255,7 +256,7 @@ async def _issue_materials_for_order_creation(
|
||||
reference_id=order.id,
|
||||
reference_no=production_no,
|
||||
unit_price=item.unit_cost,
|
||||
total_amount=round(float(total_amount), 4),
|
||||
total_amount=Decimal(str(total_amount)),
|
||||
remark=f"销售单{order.order_no}创建时自动扣减物料",
|
||||
operator_id=current_user.id
|
||||
)
|
||||
@@ -264,8 +265,8 @@ async def _issue_materials_for_order_creation(
|
||||
|
||||
order.production_no = production_no
|
||||
order.production_status = "material_issued"
|
||||
order.planned_material_cost = round(float(planned_material_cost), 4)
|
||||
order.actual_material_cost = round(float(actual_material_cost), 4)
|
||||
order.planned_material_cost = planned_material_cost
|
||||
order.actual_material_cost = actual_material_cost
|
||||
order.status = "manufacturing"
|
||||
|
||||
return movement_count, planned_material_cost, actual_material_cost
|
||||
@@ -543,8 +544,10 @@ async def consume_materials(
|
||||
"""记录销售订单的物料消耗"""
|
||||
order, customer = await _get_sales_order_with_customer(db_session, order_id)
|
||||
|
||||
default_warehouse = await _get_default_warehouse(db_session)
|
||||
|
||||
# 计算总物料成本
|
||||
total_cost = 0
|
||||
total_cost = Decimal("0")
|
||||
|
||||
# 处理每个物料消耗项
|
||||
for item in request.items:
|
||||
@@ -556,14 +559,14 @@ async def consume_materials(
|
||||
raise HTTPException(status_code=400, detail=f"只能消耗物料类型的产品: {material.name}")
|
||||
|
||||
# 计算成本
|
||||
cost = material.cost_price * item.quantity
|
||||
cost = Decimal(str(material.cost_price or 0)) * item.quantity
|
||||
total_cost += cost
|
||||
|
||||
# 更新物料库存
|
||||
inventory_result = await db_session.execute(
|
||||
select(Inventory)
|
||||
.where(Inventory.product_id == material.id)
|
||||
.where(Inventory.warehouse_id == 1)
|
||||
.where(Inventory.warehouse_id == default_warehouse.id)
|
||||
)
|
||||
inventory = inventory_result.scalar()
|
||||
if inventory:
|
||||
@@ -578,7 +581,7 @@ async def consume_materials(
|
||||
# 记录物料消耗
|
||||
movement = StockMovement(
|
||||
product_id=material.id,
|
||||
warehouse_id=1, # 默认仓库
|
||||
warehouse_id=default_warehouse.id,
|
||||
quantity=-item.quantity,
|
||||
before_quantity=before_qty,
|
||||
after_quantity=after_qty,
|
||||
@@ -620,7 +623,7 @@ async def get_sales_order_production_plan(
|
||||
order_no=order.order_no,
|
||||
customer_name=customer.name,
|
||||
production_no=production_no,
|
||||
planned_material_cost=round(float(planned_material_cost), 4),
|
||||
planned_material_cost=planned_material_cost,
|
||||
items=plan_items,
|
||||
)
|
||||
|
||||
@@ -688,7 +691,7 @@ async def issue_sales_order_materials(
|
||||
reference_id=order.id,
|
||||
reference_no=production_no,
|
||||
unit_price=item.unit_cost,
|
||||
total_amount=round(float(total_amount), 4),
|
||||
total_amount=Decimal(str(total_amount)),
|
||||
remark=payload.remark or f"销售单{order.order_no}按单生产领料",
|
||||
operator_id=current_user.id
|
||||
)
|
||||
@@ -697,22 +700,22 @@ async def issue_sales_order_materials(
|
||||
|
||||
order.production_no = production_no
|
||||
order.production_status = "material_issued"
|
||||
order.planned_material_cost = round(float(planned_material_cost), 4)
|
||||
order.actual_material_cost = round(float(actual_material_cost), 4)
|
||||
order.planned_material_cost = planned_material_cost
|
||||
order.actual_material_cost = actual_material_cost
|
||||
if order.status == "draft":
|
||||
order.status = "manufacturing"
|
||||
|
||||
await db_session.commit()
|
||||
|
||||
cost_deviation = round(float(actual_material_cost - planned_material_cost), 4)
|
||||
cost_deviation_rate = round((cost_deviation / planned_material_cost), 6) if planned_material_cost > 1e-9 else 0.0
|
||||
cost_deviation = actual_material_cost - planned_material_cost
|
||||
cost_deviation_rate = (cost_deviation / planned_material_cost) if planned_material_cost > Decimal("1e-9") else Decimal("0")
|
||||
return SalesOrderIssueResponse(
|
||||
sales_order_id=order.id,
|
||||
order_no=order.order_no,
|
||||
production_no=production_no,
|
||||
movement_count=movement_count,
|
||||
planned_material_cost=round(float(planned_material_cost), 4),
|
||||
actual_material_cost=round(float(actual_material_cost), 4),
|
||||
planned_material_cost=planned_material_cost,
|
||||
actual_material_cost=actual_material_cost,
|
||||
cost_deviation=cost_deviation,
|
||||
cost_deviation_rate=cost_deviation_rate,
|
||||
production_status=order.production_status,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Literal
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
TxnType = Literal["receipt", "payment"]
|
||||
@@ -12,11 +13,11 @@ TxnStatus = Literal["confirmed", "voided"]
|
||||
class FinanceAllocationCreate(BaseModel):
|
||||
order_type: OrderType
|
||||
order_id: int
|
||||
allocated_amount: float = Field(gt=0)
|
||||
allocated_amount: Decimal = Field(gt=0)
|
||||
|
||||
|
||||
class FinanceTransactionCreate(BaseModel):
|
||||
amount: float = Field(gt=0)
|
||||
amount: Decimal = Field(gt=0)
|
||||
txn_date: Optional[datetime] = None
|
||||
method: str = "bank"
|
||||
account_name: Optional[str] = None
|
||||
@@ -36,13 +37,11 @@ class FinanceAllocationResponse(BaseModel):
|
||||
id: int
|
||||
order_type: str
|
||||
order_id: int
|
||||
allocated_amount: float
|
||||
allocated_amount: Decimal
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
|
||||
|
||||
class FinanceTransactionResponse(BaseModel):
|
||||
id: int
|
||||
@@ -50,7 +49,7 @@ class FinanceTransactionResponse(BaseModel):
|
||||
txn_type: TxnType
|
||||
partner_type: PartnerType
|
||||
partner_id: int
|
||||
amount: float
|
||||
amount: Decimal
|
||||
txn_date: datetime
|
||||
method: str
|
||||
account_name: Optional[str]
|
||||
@@ -64,15 +63,15 @@ class FinanceTransactionResponse(BaseModel):
|
||||
|
||||
|
||||
class FinanceSummaryResponse(BaseModel):
|
||||
receivable_total: float
|
||||
payable_total: float
|
||||
monthly_receipt_total: float
|
||||
monthly_payment_total: float
|
||||
receivable_total: Decimal
|
||||
payable_total: Decimal
|
||||
monthly_receipt_total: Decimal
|
||||
monthly_payment_total: Decimal
|
||||
selected_year: int
|
||||
selected_quarter: Optional[int] = None
|
||||
period_label: str
|
||||
period_receipt_total: float = 0
|
||||
period_payment_total: float = 0
|
||||
period_receipt_total: Decimal = Decimal("0")
|
||||
period_payment_total: Decimal = Decimal("0")
|
||||
overdue_receivable_count: int = 0
|
||||
overdue_payable_count: int = 0
|
||||
|
||||
@@ -83,9 +82,9 @@ class ReceivableItemResponse(BaseModel):
|
||||
customer_id: int
|
||||
customer_name: str
|
||||
order_date: datetime
|
||||
total_amount: float
|
||||
received_amount: float
|
||||
receivable_amount: float
|
||||
total_amount: Decimal
|
||||
received_amount: Decimal
|
||||
receivable_amount: Decimal
|
||||
status: str
|
||||
|
||||
|
||||
@@ -95,9 +94,9 @@ class PayableItemResponse(BaseModel):
|
||||
supplier_id: int
|
||||
supplier_name: str
|
||||
order_date: datetime
|
||||
total_amount: float
|
||||
paid_amount: float
|
||||
payable_amount: float
|
||||
total_amount: Decimal
|
||||
paid_amount: Decimal
|
||||
payable_amount: Decimal
|
||||
status: str
|
||||
|
||||
|
||||
@@ -106,10 +105,10 @@ class PartnerStatementItemResponse(BaseModel):
|
||||
partner_name: str
|
||||
order_count: int
|
||||
transaction_count: int
|
||||
order_total: float
|
||||
settled_total: float
|
||||
transaction_total: float
|
||||
outstanding_total: float
|
||||
order_total: Decimal
|
||||
settled_total: Decimal
|
||||
transaction_total: Decimal
|
||||
outstanding_total: Decimal
|
||||
period_year: int
|
||||
period_quarter: Optional[int] = None
|
||||
|
||||
@@ -119,10 +118,10 @@ class FinancePartnerStatementResponse(BaseModel):
|
||||
year: int
|
||||
quarter: Optional[int] = None
|
||||
period_label: str
|
||||
order_total: float
|
||||
settled_total: float
|
||||
transaction_total: float
|
||||
outstanding_total: float
|
||||
order_total: Decimal
|
||||
settled_total: Decimal
|
||||
transaction_total: Decimal
|
||||
outstanding_total: Decimal
|
||||
items: List[PartnerStatementItemResponse] = []
|
||||
|
||||
|
||||
@@ -133,10 +132,10 @@ class PartnerProductStatementItemResponse(BaseModel):
|
||||
product_sku: Optional[str] = None
|
||||
product_name: str
|
||||
order_count: int
|
||||
order_quantity: float
|
||||
order_amount: float
|
||||
settled_amount: float
|
||||
outstanding_amount: float
|
||||
order_quantity: Decimal
|
||||
order_amount: Decimal
|
||||
settled_amount: Decimal
|
||||
outstanding_amount: Decimal
|
||||
period_year: int
|
||||
period_quarter: Optional[int] = None
|
||||
|
||||
@@ -147,7 +146,7 @@ class FinancePartnerProductStatementResponse(BaseModel):
|
||||
quarter: Optional[int] = None
|
||||
period_label: str
|
||||
partner_id: Optional[int] = None
|
||||
order_amount_total: float
|
||||
settled_amount_total: float
|
||||
outstanding_amount_total: float
|
||||
order_amount_total: Decimal
|
||||
settled_amount_total: Decimal
|
||||
outstanding_amount_total: Decimal
|
||||
items: List[PartnerProductStatementItemResponse] = []
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class InventoryResponse(BaseModel):
|
||||
@@ -9,9 +10,9 @@ class InventoryResponse(BaseModel):
|
||||
product_sku: str
|
||||
warehouse_id: int
|
||||
warehouse_name: str
|
||||
quantity: int
|
||||
locked_quantity: int
|
||||
available_quantity: int
|
||||
quantity: Decimal
|
||||
locked_quantity: Decimal
|
||||
available_quantity: Decimal
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -20,14 +21,14 @@ class InventoryResponse(BaseModel):
|
||||
class InventoryCreate(BaseModel):
|
||||
product_id: int
|
||||
warehouse_id: int
|
||||
quantity: int = 0
|
||||
locked_quantity: int = 0
|
||||
quantity: Decimal = Decimal("0")
|
||||
locked_quantity: Decimal = Decimal("0")
|
||||
batch_number: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
|
||||
|
||||
class InventoryUpdate(BaseModel):
|
||||
quantity: Optional[int] = None
|
||||
locked_quantity: Optional[int] = None
|
||||
quantity: Optional[Decimal] = None
|
||||
locked_quantity: Optional[Decimal] = None
|
||||
batch_number: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class ProductCreate(BaseModel):
|
||||
@@ -10,8 +11,8 @@ class ProductCreate(BaseModel):
|
||||
category: Optional[str] = None
|
||||
unit: str = "件"
|
||||
item_type: str = "finished"
|
||||
cost_price: float = 0
|
||||
sale_price: float = 0
|
||||
cost_price: Decimal = Decimal("0")
|
||||
sale_price: Decimal = Decimal("0")
|
||||
min_stock: int = 0
|
||||
max_stock: int = 1000
|
||||
|
||||
@@ -24,11 +25,11 @@ class ProductResponse(BaseModel):
|
||||
category: Optional[str]
|
||||
unit: str
|
||||
item_type: str
|
||||
cost_price: float
|
||||
sale_price: float
|
||||
cost_price: Decimal
|
||||
sale_price: Decimal
|
||||
min_stock: int
|
||||
max_stock: int
|
||||
material_cost: float = 0
|
||||
material_cost: Decimal = Decimal("0")
|
||||
is_active: bool
|
||||
created_at: datetime
|
||||
|
||||
@@ -38,7 +39,7 @@ class ProductResponse(BaseModel):
|
||||
|
||||
class ProductMaterialItemUpdate(BaseModel):
|
||||
material_id: int
|
||||
quantity: float
|
||||
quantity: Decimal
|
||||
|
||||
|
||||
class ProductBOMUpdate(BaseModel):
|
||||
@@ -49,13 +50,13 @@ class ProductMaterialItemResponse(BaseModel):
|
||||
material_id: int
|
||||
material_sku: str
|
||||
material_name: str
|
||||
quantity: float
|
||||
unit_cost: float
|
||||
line_cost: float
|
||||
quantity: Decimal
|
||||
unit_cost: Decimal
|
||||
line_cost: Decimal
|
||||
|
||||
|
||||
class ProductBOMResponse(BaseModel):
|
||||
product_id: int
|
||||
product_name: str
|
||||
total_material_cost: float
|
||||
total_material_cost: Decimal
|
||||
items: List[ProductMaterialItemResponse]
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class PurchaseOrderItemCreate(BaseModel):
|
||||
product_id: int
|
||||
quantity: int = Field(..., gt=0, description="采购数量")
|
||||
unit_price: Optional[float] = Field(None, description="单价(可选,后端自动使用物料成本价格)")
|
||||
unit_price: Optional[Decimal] = Field(None, description="单价(可选,后端自动使用物料成本价格)")
|
||||
remark: Optional[str] = None
|
||||
|
||||
|
||||
@@ -24,8 +25,8 @@ class PurchaseOrderResponse(BaseModel):
|
||||
order_date: datetime
|
||||
expected_date: Optional[date]
|
||||
status: str
|
||||
total_amount: float
|
||||
paid_amount: float
|
||||
total_amount: Decimal
|
||||
paid_amount: Decimal
|
||||
remark: Optional[str]
|
||||
created_at: datetime
|
||||
received_date: Optional[datetime]
|
||||
@@ -42,8 +43,8 @@ class PurchaseOrderItemResponse(BaseModel):
|
||||
product_name: str
|
||||
quantity: int
|
||||
received_quantity: int
|
||||
unit_price: float
|
||||
amount: float
|
||||
unit_price: Decimal
|
||||
amount: Decimal
|
||||
remark: Optional[str] = None
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class SalesOrderItemCreate(BaseModel):
|
||||
@@ -10,7 +11,7 @@ class SalesOrderItemCreate(BaseModel):
|
||||
product_category: Optional[str] = None
|
||||
product_unit: Optional[str] = "件"
|
||||
quantity: int = Field(gt=0)
|
||||
unit_price: float = Field(ge=0)
|
||||
unit_price: Decimal = Field(ge=0)
|
||||
remark: Optional[str] = None
|
||||
|
||||
|
||||
@@ -33,10 +34,10 @@ class SalesOrderResponse(BaseModel):
|
||||
status: str
|
||||
production_status: str
|
||||
production_no: Optional[str]
|
||||
planned_material_cost: float
|
||||
actual_material_cost: float
|
||||
total_amount: float
|
||||
received_amount: float
|
||||
planned_material_cost: Decimal
|
||||
actual_material_cost: Decimal
|
||||
total_amount: Decimal
|
||||
received_amount: Decimal
|
||||
remark: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
@@ -49,8 +50,8 @@ class SalesOrderItemResponse(BaseModel):
|
||||
product_id: int
|
||||
quantity: int
|
||||
delivered_quantity: int
|
||||
unit_price: float
|
||||
amount: float
|
||||
unit_price: Decimal
|
||||
amount: Decimal
|
||||
remark: Optional[str] = None
|
||||
|
||||
|
||||
@@ -63,11 +64,11 @@ class ProductionMaterialPlanItemResponse(BaseModel):
|
||||
material_id: int
|
||||
material_sku: str
|
||||
material_name: str
|
||||
required_quantity: int
|
||||
available_quantity: int
|
||||
shortage_quantity: int
|
||||
unit_cost: float
|
||||
required_cost: float
|
||||
required_quantity: Decimal
|
||||
available_quantity: Decimal
|
||||
shortage_quantity: Decimal
|
||||
unit_cost: Decimal
|
||||
required_cost: Decimal
|
||||
|
||||
|
||||
class SalesOrderProductionPlanResponse(BaseModel):
|
||||
@@ -75,7 +76,7 @@ class SalesOrderProductionPlanResponse(BaseModel):
|
||||
order_no: str
|
||||
customer_name: str
|
||||
production_no: str
|
||||
planned_material_cost: float
|
||||
planned_material_cost: Decimal
|
||||
items: List[ProductionMaterialPlanItemResponse]
|
||||
|
||||
|
||||
@@ -90,10 +91,10 @@ class SalesOrderIssueResponse(BaseModel):
|
||||
order_no: str
|
||||
production_no: str
|
||||
movement_count: int
|
||||
planned_material_cost: float
|
||||
actual_material_cost: float
|
||||
cost_deviation: float
|
||||
cost_deviation_rate: float
|
||||
planned_material_cost: Decimal
|
||||
actual_material_cost: Decimal
|
||||
cost_deviation: Decimal
|
||||
cost_deviation_rate: Decimal
|
||||
production_status: str
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class StockMovementCreate(BaseModel):
|
||||
@@ -8,8 +9,8 @@ class StockMovementCreate(BaseModel):
|
||||
product_sku: Optional[str] = None
|
||||
warehouse_id: int
|
||||
movement_type: str
|
||||
quantity: int
|
||||
unit_price: Optional[float] = None
|
||||
quantity: Decimal
|
||||
unit_price: Optional[Decimal] = None
|
||||
remark: Optional[str] = None
|
||||
|
||||
|
||||
@@ -19,9 +20,9 @@ class StockMovementResponse(BaseModel):
|
||||
product_sku: Optional[str]
|
||||
product_name: str
|
||||
movement_type: str
|
||||
quantity: int
|
||||
before_quantity: int
|
||||
after_quantity: int
|
||||
quantity: Decimal
|
||||
before_quantity: Decimal
|
||||
after_quantity: Decimal
|
||||
reference_no: Optional[str]
|
||||
remark: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
+353
-1
@@ -3,6 +3,7 @@ from fastapi import APIRouter, UploadFile, File, HTTPException, BackgroundTasks,
|
||||
from typing import Optional, Dict, Any, List
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from models.schemas import ProcessingStatus, create_task_info
|
||||
@@ -18,6 +19,12 @@ from core.mold_generator import MoldCavityGenerator
|
||||
from core.aluminum_foam_mold import AluminumFoamMoldGenerator
|
||||
from core.mold_quality_inspector import AluminumFoamMoldQualityInspector
|
||||
from core.mesh_generator import MeshGenerator
|
||||
from core.cavity_layout_optimizer import CavityLayoutOptimizer
|
||||
from core.mold_system_designer import MoldSystemDesigner
|
||||
from core.side_action_designer import SideActionDesigner
|
||||
from core.mold_cam import MoldCAMDesigner
|
||||
from core.mold_machining import CollisionDetector, ToolpathOptimizer, EDMElectrodeDesigner, MachiningSimulator
|
||||
from core.cad_exporter import CADExporter
|
||||
from services.auth_service import get_current_active_user
|
||||
from models.database import User
|
||||
|
||||
@@ -35,6 +42,15 @@ aluminum_foam_generator = AluminumFoamMoldGenerator(shrinkage_rate=0.015, draft_
|
||||
# 铝泡沫模具质量检测器
|
||||
mold_quality_inspector = AluminumFoamMoldQualityInspector()
|
||||
mesh_generator = MeshGenerator(quality="medium")
|
||||
cavity_layout_optimizer = CavityLayoutOptimizer()
|
||||
mold_system_designer = MoldSystemDesigner()
|
||||
side_action_designer = SideActionDesigner()
|
||||
mold_cam_designer = MoldCAMDesigner()
|
||||
collision_detector = CollisionDetector()
|
||||
toolpath_optimizer = ToolpathOptimizer()
|
||||
edm_designer = EDMElectrodeDesigner()
|
||||
machining_simulator = MachiningSimulator()
|
||||
cad_exporter = CADExporter()
|
||||
|
||||
tasks = {}
|
||||
|
||||
@@ -348,6 +364,341 @@ async def process_file_with_storage(
|
||||
tasks[task_id]["completed_at"] = str(datetime.now())
|
||||
|
||||
|
||||
# ==================== P3 新增 API ====================
|
||||
|
||||
@router.post("/optimize-layout")
|
||||
async def optimize_cavity_layout(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""多型腔布局优化"""
|
||||
body = await request.json()
|
||||
product_bbox = body.get("product_bbox", {"dimensions": [100, 100, 50]})
|
||||
cavity_count = body.get("cavity_count", 1)
|
||||
mold_base_size = body.get("mold_base_size")
|
||||
layout_type = body.get("layout_type", "auto")
|
||||
|
||||
if cavity_count < 1 or cavity_count > 64:
|
||||
raise HTTPException(400, "型腔数量必须在 1-64 之间")
|
||||
|
||||
result = cavity_layout_optimizer.optimize_layout(
|
||||
product_bbox=product_bbox,
|
||||
cavity_count=cavity_count,
|
||||
mold_base_size=mold_base_size,
|
||||
layout_type=layout_type,
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/design-cooling")
|
||||
async def design_cooling_system(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""冷却系统设计"""
|
||||
body = await request.json()
|
||||
mold_size = body.get("mold_size", {"length": 300, "width": 300, "height": 200})
|
||||
product_bbox = body.get("product_bbox", {"dimensions": [100, 100, 50]})
|
||||
material = body.get("material", "ABS")
|
||||
cavity_count = body.get("cavity_count", 1)
|
||||
cycle_time_target = body.get("cycle_time_target")
|
||||
|
||||
from core.mold_system_designer import CoolingSystemDesigner
|
||||
designer = CoolingSystemDesigner()
|
||||
result = designer.design_cooling_system(
|
||||
mold_size=mold_size,
|
||||
product_bbox=product_bbox,
|
||||
material=material,
|
||||
cavity_count=cavity_count,
|
||||
cycle_time_target=cycle_time_target,
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/design-gating")
|
||||
async def design_gating_system(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""浇注系统设计"""
|
||||
body = await request.json()
|
||||
product_bbox = body.get("product_bbox", {"dimensions": [100, 100, 50]})
|
||||
material = body.get("material", "ABS")
|
||||
cavity_count = body.get("cavity_count", 1)
|
||||
gate_type = body.get("gate_type", "auto")
|
||||
layout_positions = body.get("layout_positions")
|
||||
|
||||
from core.mold_system_designer import GatingSystemDesigner
|
||||
designer = GatingSystemDesigner()
|
||||
result = designer.design_gating_system(
|
||||
product_bbox=product_bbox,
|
||||
material=material,
|
||||
cavity_count=cavity_count,
|
||||
gate_type=gate_type,
|
||||
layout_positions=layout_positions,
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/design-mold-system")
|
||||
async def design_complete_mold_system(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""综合模具系统设计(冷却+浇注)"""
|
||||
body = await request.json()
|
||||
mold_size = body.get("mold_size", {"length": 300, "width": 300, "height": 200})
|
||||
product_bbox = body.get("product_bbox", {"dimensions": [100, 100, 50]})
|
||||
material = body.get("material", "ABS")
|
||||
cavity_count = body.get("cavity_count", 1)
|
||||
gate_type = body.get("gate_type", "auto")
|
||||
cycle_time_target = body.get("cycle_time_target")
|
||||
layout_positions = body.get("layout_positions")
|
||||
|
||||
result = mold_system_designer.design_complete_system(
|
||||
mold_size=mold_size,
|
||||
product_bbox=product_bbox,
|
||||
material=material,
|
||||
cavity_count=cavity_count,
|
||||
gate_type=gate_type,
|
||||
cycle_time_target=cycle_time_target,
|
||||
layout_positions=layout_positions,
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/ai-parting-detect")
|
||||
async def ai_parting_surface_detect(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""AI 分型面检测"""
|
||||
body = await request.json()
|
||||
task_id = body.get("task_id")
|
||||
|
||||
if not task_id or task_id not in tasks:
|
||||
raise HTTPException(404, "任务不存在")
|
||||
|
||||
task_data = tasks[task_id]
|
||||
geometry_data = task_data.get("geometry_data")
|
||||
if not geometry_data:
|
||||
raise HTTPException(400, "该任务尚未完成几何分析")
|
||||
|
||||
from core.ai_parting_detector import AIPartingSurfaceDetectorV2
|
||||
detector = AIPartingSurfaceDetectorV2(use_gnn=True)
|
||||
|
||||
result = detector._detect_with_geometry(None, geometry_data)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/detect-undercuts")
|
||||
async def detect_undercuts(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""倒扣区域检测与滑块/斜顶机构设计"""
|
||||
body = await request.json()
|
||||
task_id = body.get("task_id")
|
||||
parting_direction = body.get("parting_direction", [0, 0, 1])
|
||||
mold_size = body.get("mold_size", {"length": 300, "width": 300, "height": 200})
|
||||
|
||||
if not task_id or task_id not in tasks:
|
||||
raise HTTPException(404, "任务不存在")
|
||||
|
||||
task_data = tasks[task_id]
|
||||
geometry_data = task_data.get("geometry_data")
|
||||
if not geometry_data:
|
||||
raise HTTPException(400, "该任务尚未完成几何分析")
|
||||
|
||||
result = side_action_designer.analyze_and_design(
|
||||
shape=None, parting_direction=parting_direction, mold_size=mold_size
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/design-cam")
|
||||
async def design_mold_cam(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""模具CAM刀路设计"""
|
||||
body = await request.json()
|
||||
cavity_bbox = body.get("cavity_bbox", {"dimensions": [100, 100, 50], "min": [-50, -50, -25], "max": [50, 50, 25]})
|
||||
stock_bbox = body.get("stock_bbox", {"dimensions": [150, 150, 100], "min": [-75, -75, -50], "max": [75, 75, 50]})
|
||||
mold_steel = body.get("mold_steel", "P20")
|
||||
surface_quality = body.get("surface_quality", "standard")
|
||||
controller = body.get("controller", "fanuc")
|
||||
|
||||
result = mold_cam_designer.design_mold_cam(
|
||||
cavity_bbox=cavity_bbox,
|
||||
stock_bbox=stock_bbox,
|
||||
mold_steel=mold_steel,
|
||||
surface_quality=surface_quality,
|
||||
controller=controller,
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/check-collision")
|
||||
async def check_toolpath_collision(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""刀路碰撞检测"""
|
||||
body = await request.json()
|
||||
toolpath_points = body.get("toolpath_points", [[0, 0, 50], [10, 10, -5], [20, 20, -10]])
|
||||
tool = body.get("tool", {"diameter": 10, "flute_length": 30, "shank_diameter": 10})
|
||||
stock_bbox = body.get("stock_bbox", {"min": [-50, -50, -25], "max": [50, 50, 25]})
|
||||
clamp_positions = body.get("clamp_positions")
|
||||
|
||||
result = collision_detector.check_toolpath_safety(
|
||||
toolpath_points, tool, stock_bbox, clamp_positions
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/optimize-toolpath")
|
||||
async def optimize_toolpath(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""刀路优化"""
|
||||
body = await request.json()
|
||||
toolpath_points = body.get("toolpath_points", [[0, 0, 50], [10, 10, -5], [20, 20, -10]])
|
||||
cutting_params = body.get("cutting_params", {"feed_rate_mm_min": 500})
|
||||
stock_bbox = body.get("stock_bbox")
|
||||
|
||||
result = toolpath_optimizer.optimize_toolpath(
|
||||
toolpath_points, cutting_params, stock_bbox
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/design-electrodes")
|
||||
async def design_edm_electrodes(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""EDM电极设计"""
|
||||
body = await request.json()
|
||||
undercut_regions = body.get("undercut_regions", [{"center": [0, 0, 0], "area": 100, "type": "undercut"}])
|
||||
cavity_bbox = body.get("cavity_bbox", {"dimensions": [100, 100, 50]})
|
||||
material = body.get("material", "copper")
|
||||
spark_gap = body.get("spark_gap", 0.05)
|
||||
overburn = body.get("overburn", 0.1)
|
||||
|
||||
result = edm_designer.design_electrodes(
|
||||
undercut_regions, cavity_bbox, material, spark_gap, overburn
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/simulate-machining")
|
||||
async def simulate_machining(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""加工仿真"""
|
||||
body = await request.json()
|
||||
operations = body.get("operations", [{"strategy": "z_level_roughing", "levels": [{"z": -5}]}])
|
||||
stock_bbox = body.get("stock_bbox", {"dimensions": [100, 100, 50], "min": [-50, -50, -25], "max": [50, 50, 25]})
|
||||
resolution = body.get("resolution", 2.0)
|
||||
|
||||
result = machining_simulator.simulate_machining(
|
||||
operations, stock_bbox, resolution
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
# ==================== CAD 导出 API ====================
|
||||
|
||||
@router.post("/export-mold")
|
||||
async def export_mold_results(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""导出模具设计结果(STEP/IGES/STL/BRep)"""
|
||||
body = await request.json()
|
||||
task_id = body.get("task_id")
|
||||
formats = body.get("formats", ["step", "stl"])
|
||||
components = body.get("components", ["cavity", "core"])
|
||||
|
||||
if not task_id or task_id not in tasks:
|
||||
raise HTTPException(404, "任务不存在")
|
||||
|
||||
task_data = tasks[task_id]
|
||||
cavity_shapes = task_data.get("cavity_shapes")
|
||||
if not cavity_shapes:
|
||||
raise HTTPException(400, "该任务尚未完成模具生成或形状数据不可用")
|
||||
|
||||
base_filename = Path(task_data.get("filename", f"mold_{task_id}")).stem
|
||||
|
||||
result = cad_exporter.export_mold_results(
|
||||
cavity_data=cavity_shapes,
|
||||
base_filename=base_filename,
|
||||
formats=formats,
|
||||
components=components,
|
||||
)
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@router.get("/export-download/{filepath:path}")
|
||||
async def download_export_file(
|
||||
filepath: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""下载导出的CAD文件"""
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
full_path = os.path.join(cad_exporter.output_dir, filepath)
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
raise HTTPException(404, "文件不存在")
|
||||
|
||||
if not os.path.abspath(full_path).startswith(os.path.abspath(cad_exporter.output_dir)):
|
||||
raise HTTPException(403, "禁止访问")
|
||||
|
||||
media_types = {
|
||||
".step": "application/step",
|
||||
".stp": "application/step",
|
||||
".iges": "application/iges",
|
||||
".igs": "application/iges",
|
||||
".stl": "model/stl",
|
||||
".brep": "application/octet-stream",
|
||||
}
|
||||
|
||||
ext = Path(full_path).suffix.lower()
|
||||
media_type = media_types.get(ext, "application/octet-stream")
|
||||
|
||||
return FileResponse(
|
||||
full_path,
|
||||
media_type=media_type,
|
||||
filename=os.path.basename(full_path),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/export-recommendations")
|
||||
async def get_export_recommendations(
|
||||
target: str = "ug",
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""获取导出格式建议(UG/FreeCAD/SolidWorks)"""
|
||||
result = cad_exporter.get_export_recommendations(target)
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
async def _save_analysis_metrics(session, stp_file_id, analysis_result):
|
||||
"""保存分析指标到数据库"""
|
||||
from models.database import AnalysisMetrics
|
||||
@@ -774,7 +1125,7 @@ async def process_file_core(
|
||||
)
|
||||
|
||||
# 9. 分析模具设计
|
||||
analysis_result = geometry_analyzer.analyze_mold_design(geometry_data)
|
||||
analysis_result = geometry_analyzer.analyze_mold_design(geometry_data, shape=shape)
|
||||
|
||||
# 9.5 保存完整的分析结果到数据库
|
||||
if analysis_result:
|
||||
@@ -838,6 +1189,7 @@ async def process_file_core(
|
||||
tasks[task_id]["geometry_data"] = geometry_data
|
||||
tasks[task_id]["analysis_result"] = analysis_result
|
||||
tasks[task_id]["cavity_data"] = detailed_cavity_json
|
||||
tasks[task_id]["cavity_shapes"] = cavity_result
|
||||
tasks[task_id]["key_info"] = detailed_cavity_json # 传递完整数据给前端
|
||||
tasks[task_id]["html_file"] = f"/html/{Path(html_file_path).name}" # 只使用文件名
|
||||
tasks[task_id]["verification"] = verification_result # 添加验证结果
|
||||
|
||||
Reference in New Issue
Block a user