init
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user