init
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, delete
|
||||
from sqlalchemy import select, func, delete, update
|
||||
from typing import Optional, List
|
||||
from math import ceil
|
||||
|
||||
@@ -51,6 +51,9 @@ def _build_sales_order_response(order: SalesOrder, customer_name: str) -> SalesO
|
||||
customer_name=customer_name,
|
||||
order_date=order.order_date,
|
||||
delivery_date=order.delivery_date,
|
||||
manufacturing_date=order.manufacturing_date,
|
||||
actual_delivery_date=order.actual_delivery_date,
|
||||
actual_payment_date=order.actual_payment_date,
|
||||
status=order.status,
|
||||
production_status=order.production_status or "not_started",
|
||||
production_no=order.production_no,
|
||||
@@ -79,6 +82,9 @@ async def _build_sales_order_detail_response(
|
||||
customer_name=customer_name,
|
||||
order_date=order.order_date,
|
||||
delivery_date=order.delivery_date,
|
||||
manufacturing_date=order.manufacturing_date,
|
||||
actual_delivery_date=order.actual_delivery_date,
|
||||
actual_payment_date=order.actual_payment_date,
|
||||
status=order.status,
|
||||
production_status=order.production_status or "not_started",
|
||||
production_no=order.production_no,
|
||||
@@ -125,27 +131,28 @@ async def _build_material_plan(db_session: AsyncSession, order: SalesOrder) -> t
|
||||
if not order_items:
|
||||
return [], 0
|
||||
|
||||
finished_ids = list({int(i.product_id) for i in order_items})
|
||||
bom_result = await db_session.execute(
|
||||
select(ProductMaterial, Product)
|
||||
.join(Product, ProductMaterial.material_product_id == Product.id)
|
||||
.where(ProductMaterial.finished_product_id.in_(finished_ids))
|
||||
.where(Product.is_active == True)
|
||||
.where(Product.item_type == "material")
|
||||
)
|
||||
bom_rows = bom_result.all()
|
||||
if not bom_rows:
|
||||
return [], 0
|
||||
|
||||
bom_by_finished_id = {}
|
||||
for bom, material in bom_rows:
|
||||
bom_by_finished_id.setdefault(int(bom.finished_product_id), []).append((bom, material))
|
||||
|
||||
required_qty_map = {}
|
||||
for order_item in order_items:
|
||||
bom_result = await db_session.execute(
|
||||
select(ProductMaterial, Product)
|
||||
.join(Product, ProductMaterial.material_product_id == Product.id)
|
||||
.where(ProductMaterial.finished_product_id == order_item.product_id)
|
||||
.where(Product.is_active == True)
|
||||
.where(Product.item_type == "material")
|
||||
)
|
||||
bom_items = bom_result.all()
|
||||
if not bom_items:
|
||||
continue
|
||||
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
|
||||
}
|
||||
)
|
||||
entry = required_qty_map.setdefault(material.id, {"material": material, "required_qty": 0.0})
|
||||
entry["required_qty"] += qty
|
||||
|
||||
if not required_qty_map:
|
||||
@@ -220,18 +227,19 @@ async def _issue_materials_for_order_creation(
|
||||
movement_count = 0
|
||||
|
||||
for item in plan_items:
|
||||
inv_result = await db_session.execute(
|
||||
select(Inventory)
|
||||
upd_result = await db_session.execute(
|
||||
update(Inventory)
|
||||
.where(Inventory.product_id == item.material_id)
|
||||
.where(Inventory.warehouse_id == warehouse.id)
|
||||
.where(Inventory.quantity >= item.required_quantity)
|
||||
.values(quantity=Inventory.quantity - item.required_quantity)
|
||||
.returning(Inventory.quantity)
|
||||
)
|
||||
inventory = inv_result.scalar_one_or_none()
|
||||
if not inventory or inventory.quantity < item.required_quantity:
|
||||
after_qty = upd_result.scalar_one_or_none()
|
||||
if after_qty is None:
|
||||
raise HTTPException(status_code=400, detail=f"{item.material_name} 在默认仓库库存不足")
|
||||
|
||||
before_qty = inventory.quantity
|
||||
inventory.quantity -= item.required_quantity
|
||||
after_qty = inventory.quantity
|
||||
after_qty = int(after_qty)
|
||||
before_qty = after_qty + int(item.required_quantity)
|
||||
total_amount = item.required_quantity * item.unit_cost
|
||||
actual_material_cost += total_amount
|
||||
|
||||
@@ -401,10 +409,15 @@ async def create_sales_order(
|
||||
db_session: AsyncSession = Depends(get_db_session),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
order = SalesOrder(
|
||||
order_no=generate_order_no("SO"),
|
||||
customer_id=order_data.customer_id,
|
||||
order_date=now,
|
||||
delivery_date=order_data.delivery_date,
|
||||
manufacturing_date=now.date(),
|
||||
created_at=now,
|
||||
remark=order_data.remark,
|
||||
operator_id=current_user.id,
|
||||
status="manufacturing"
|
||||
@@ -441,6 +454,8 @@ async def update_sales_order(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
order, customer = await _get_sales_order_with_customer(db_session, order_id)
|
||||
if order.status == "delivered":
|
||||
raise HTTPException(status_code=400, detail="已交付的销售订单禁止修改")
|
||||
await _rollback_issued_materials(db_session, order, current_user)
|
||||
await db_session.execute(delete(SalesOrderItem).where(SalesOrderItem.order_id == order.id))
|
||||
|
||||
@@ -475,6 +490,17 @@ async def update_sales_order_status(
|
||||
raise HTTPException(status_code=400, detail="订单状态必须为 manufacturing、delivered、paid")
|
||||
|
||||
order, customer = await _get_sales_order_with_customer(db_session, order_id)
|
||||
# 只禁止从已交付状态改为非已收款状态
|
||||
if order.status == "delivered" and payload.status != "paid":
|
||||
raise HTTPException(status_code=400, detail="已交付的销售订单只能修改为已收款状态")
|
||||
|
||||
# 根据状态更新相应的日期字段
|
||||
from datetime import datetime
|
||||
if payload.status == "delivered" and not order.actual_delivery_date:
|
||||
order.actual_delivery_date = datetime.now()
|
||||
elif payload.status == "paid" and not order.actual_payment_date:
|
||||
order.actual_payment_date = datetime.now()
|
||||
|
||||
order.status = payload.status
|
||||
await db_session.commit()
|
||||
await db_session.refresh(order)
|
||||
@@ -488,6 +514,8 @@ async def delete_sales_order(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
order, _ = await _get_sales_order_with_customer(db_session, order_id)
|
||||
if order.status == "delivered":
|
||||
raise HTTPException(status_code=400, detail="已交付的销售订单禁止删除")
|
||||
await _rollback_issued_materials(db_session, order, current_user)
|
||||
await db_session.delete(order)
|
||||
await db_session.commit()
|
||||
@@ -522,6 +550,8 @@ async def issue_sales_order_materials(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
order, _ = await _get_sales_order_with_customer(db_session, order_id)
|
||||
if order.status == "delivered":
|
||||
raise HTTPException(status_code=400, detail="已交付的销售订单禁止领料")
|
||||
if order.production_status == "completed":
|
||||
raise HTTPException(status_code=400, detail="该销售单已完成生产")
|
||||
if order.production_status == "material_issued":
|
||||
@@ -548,18 +578,19 @@ async def issue_sales_order_materials(
|
||||
actual_material_cost = 0.0
|
||||
movement_count = 0
|
||||
for item in plan_items:
|
||||
inv_result = await db_session.execute(
|
||||
select(Inventory)
|
||||
upd_result = await db_session.execute(
|
||||
update(Inventory)
|
||||
.where(Inventory.product_id == item.material_id)
|
||||
.where(Inventory.warehouse_id == warehouse.id)
|
||||
.where(Inventory.quantity >= item.required_quantity)
|
||||
.values(quantity=Inventory.quantity - item.required_quantity)
|
||||
.returning(Inventory.quantity)
|
||||
)
|
||||
inventory = inv_result.scalar_one_or_none()
|
||||
if not inventory or inventory.quantity < item.required_quantity:
|
||||
after_qty = upd_result.scalar_one_or_none()
|
||||
if after_qty is None:
|
||||
raise HTTPException(status_code=400, detail=f"{item.material_name} 在所选仓库库存不足")
|
||||
|
||||
before_qty = inventory.quantity
|
||||
inventory.quantity -= item.required_quantity
|
||||
after_qty = inventory.quantity
|
||||
after_qty = int(after_qty)
|
||||
before_qty = after_qty + int(item.required_quantity)
|
||||
total_amount = item.required_quantity * item.unit_cost
|
||||
actual_material_cost += total_amount
|
||||
|
||||
@@ -586,7 +617,7 @@ async def issue_sales_order_materials(
|
||||
order.planned_material_cost = round(float(planned_material_cost), 4)
|
||||
order.actual_material_cost = round(float(actual_material_cost), 4)
|
||||
if order.status == "draft":
|
||||
order.status = "pending"
|
||||
order.status = "manufacturing"
|
||||
|
||||
await db_session.commit()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user