dingdanquanliucheng/backend/app/services/order_service.py

443 lines
21 KiB
Python

from datetime import datetime
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from backend.app.core.error_codes import ErrorCode
from backend.app.core.exceptions import AppException
from backend.app.repositories.customer_repository import CustomerRepository
from backend.app.repositories.order_approve_log_repository import OrderApproveLogRepository
from backend.app.repositories.order_repository import OrderRepository
from backend.app.services.demo_store import demo_store
class OrderService:
def __init__(self) -> None:
self.order_repository = OrderRepository()
self.customer_repository = CustomerRepository()
self.order_approve_log_repository = OrderApproveLogRepository()
def list_orders(self, session: Session | None = None, filters: dict | None = None) -> list[dict]:
if session is not None:
try:
orders = self.order_repository.list_orders_by_filters(session, filters or {})
return [
{
"order_id": item.id,
"order_no": item.order_no,
"customer_name": item.customer_name,
"customer_mobile": item.customer_mobile,
"salesman_name": "",
"order_status": item.order_status,
"sale_price_total": float(item.sale_price_total or 0),
"profit_total": float(item.profit_total or 0),
"commission_amount": float(item.commission_amount or 0),
"created_at": item.created_at.strftime("%Y-%m-%d %H:%M:%S") if item.created_at else "",
}
for item in orders
]
except SQLAlchemyError:
pass
return demo_store.list_orders()
def get_order(self, order_id: int, session: Session | None = None) -> dict | None:
if session is not None:
try:
order = self.order_repository.get_order(session, order_id)
if order is None:
return None
supplier = self.order_repository.get_supplier(session, order.factory_id)
items = self.order_repository.list_order_items(session, order.id)
approve_logs = self.order_approve_log_repository.list_by_order_id(session, order.id)
return {
"order_id": order.id,
"order_no": order.order_no,
"customer_id": order.customer_id,
"customer_name": order.customer_name,
"customer_mobile": order.customer_mobile,
"customer_address": order.customer_address,
"salesman_id": order.salesman_id,
"salesman_name": "",
"order_source": order.order_source,
"order_status": order.order_status,
"delivery_type": order.delivery_type,
"factory_id": order.factory_id,
"factory_name": supplier.supplier_name if supplier else "",
"sale_price_total": float(order.sale_price_total or 0),
"cost_price_total": float(order.cost_price_total or 0),
"rebate_total": float(order.rebate_total or 0),
"freight_total": float(order.freight_total or 0),
"tax_total": float(order.tax_total or 0),
"other_fee_total": float(order.other_fee_total or 0),
"profit_total": float(order.profit_total or 0),
"profit_rate": float(order.profit_rate or 0),
"commission_amount": float(order.commission_amount or 0),
"items": [
{
"item_id": item.id,
"product_id": item.product_id,
"product_name": item.product_name,
"specification": item.specification,
"unit": item.unit,
"quantity": float(item.quantity or 0),
"sale_price": float(item.sale_price or 0),
"cost_price": float(item.cost_price or 0),
"rebate_amount": float(item.rebate_amount or 0),
"freight_amount": float(item.freight_amount or 0),
"tax_amount": float(item.tax_amount or 0),
"other_fee_amount": float(item.other_fee_amount or 0),
"remark": item.remark,
}
for item in items
],
"approve_logs": [
{
"log_id": log.id,
"approve_type": log.approve_type,
"approve_result": log.approve_result,
"before_status": log.before_status,
"after_status": log.after_status,
"approve_opinion": log.approve_opinion,
"operator_id": log.operator_id,
"approve_time": log.created_at.strftime("%Y-%m-%d %H:%M:%S") if log.created_at else "",
}
for log in approve_logs
],
"logistics_info": {"task": {}, "trace_list": []},
"attachments": [],
"remark": order.remark,
}
except SQLAlchemyError:
pass
return demo_store.get_order(order_id)
def submit_order(self, order_id: int, session: Session | None = None) -> dict | None:
if session is not None:
try:
order = self.order_repository.get_order(session, order_id)
if order is None:
return None
self._ensure_status(order.order_status, {"draft", "rejected"}, "当前状态不允许提交审核")
self.order_repository.update_order_status(session, order, "pending_approve")
session.commit()
return {
"order_id": order.id,
"order_status": order.order_status,
"submitted_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
except SQLAlchemyError:
session.rollback()
order = demo_store.get_order(order_id)
if order is None:
return None
self._ensure_status(order["order_status"], {"draft", "rejected"}, "当前状态不允许提交审核")
order["order_status"] = "pending_approve"
order["submitted_at"] = "2026-05-14 12:30:00"
return order
def cancel_order(self, order_id: int, payload: dict, session: Session | None = None) -> dict | None:
if session is not None:
try:
order = self.order_repository.get_order(session, order_id)
if order is None:
return None
if order.order_status in {"canceled", "settled"}:
raise AppException(
code=ErrorCode.INVALID_STATUS,
message="当前状态不允许取消订单",
status_code=400,
)
next_status = "canceled" if order.order_status in {"draft", "pending_approve"} else "cancel_pending"
previous_status = order.order_status
self.order_repository.update_cancel_fields(
session,
order,
{
"order_status": next_status,
"cancel_reason": payload["cancel_reason"],
"cancel_opinion": payload.get("cancel_opinion"),
"cancel_previous_status": None if next_status == "canceled" else previous_status,
},
)
session.commit()
return {
"order_id": order.id,
"order_status": order.order_status,
"previous_status": previous_status,
"canceled_at": None,
}
except SQLAlchemyError:
session.rollback()
order = demo_store.get_order(order_id)
if order is None:
return None
if order["order_status"] in {"canceled", "settled"}:
raise AppException(code=ErrorCode.INVALID_STATUS, message="当前状态不允许取消订单", status_code=400)
return {
"order_id": order_id,
"order_status": "canceled" if order["order_status"] in {"draft", "pending_approve"} else "cancel_pending",
"previous_status": order["order_status"],
"canceled_at": None,
}
def approve_order(self, order_id: int, payload: dict, session: Session | None = None) -> dict | None:
if session is not None:
try:
order = self.order_repository.get_order(session, order_id)
if order is None:
return None
self._ensure_status(order.order_status, {"pending_approve"}, "当前状态不允许审批")
previous_status = order.order_status
target_status = "approved" if payload["approve_result"] == "pass" else "rejected"
self.order_repository.update_order_status(session, order, target_status)
self.order_approve_log_repository.create_log(
session,
{
"order_id": order.id,
"approve_type": "order",
"approve_result": payload["approve_result"],
"before_status": previous_status,
"after_status": target_status,
"approve_opinion": payload.get("approve_opinion"),
"operator_id": None,
},
)
session.commit()
return {
"order_id": order.id,
"order_status": order.order_status,
"profit_total": float(order.profit_total or 0),
"profit_rate": float(order.profit_rate or 0),
"approve_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"approve_result": payload["approve_result"],
"approve_opinion": payload.get("approve_opinion"),
}
except SQLAlchemyError:
session.rollback()
order = demo_store.get_order(order_id)
if order is None:
return None
self._ensure_status(order["order_status"], {"pending_approve"}, "当前状态不允许审批")
return {
"order_id": order_id,
"order_status": "approved" if payload["approve_result"] == "pass" else "rejected",
"profit_total": order.get("profit_total", 0),
"profit_rate": order.get("profit_rate", 0),
"approve_time": "2026-05-14 10:30:00",
"approve_result": payload["approve_result"],
"approve_opinion": payload.get("approve_opinion"),
}
def cancel_approve_order(self, order_id: int, payload: dict, session: Session | None = None) -> dict | None:
if session is not None:
try:
order = self.order_repository.get_order(session, order_id)
if order is None:
return None
self._ensure_status(order.order_status, {"cancel_pending"}, "当前状态不允许取消审批")
previous_status = order.order_status
target_status = "canceled" if payload["approve_result"] == "pass" else (order.cancel_previous_status or "approved")
self.order_repository.update_order_status(session, order, target_status)
self.order_approve_log_repository.create_log(
session,
{
"order_id": order.id,
"approve_type": "cancel_order",
"approve_result": payload["approve_result"],
"before_status": previous_status,
"after_status": target_status,
"approve_opinion": payload.get("approve_opinion"),
"operator_id": None,
},
)
session.commit()
return {
"order_id": order.id,
"order_status": order.order_status,
"approve_result": payload["approve_result"],
"approve_opinion": payload.get("approve_opinion"),
}
except SQLAlchemyError:
session.rollback()
order = demo_store.get_order(order_id)
if order is None:
return None
self._ensure_status(order["order_status"], {"cancel_pending"}, "当前状态不允许取消审批")
return {
"order_id": order_id,
"order_status": "canceled" if payload["approve_result"] == "pass" else "approved",
"approve_result": payload["approve_result"],
"approve_opinion": payload.get("approve_opinion"),
}
def get_supplier_text(self, order_id: int, payload: dict, session: Session | None = None) -> dict | None:
if session is not None:
try:
order = self.order_repository.get_order(session, order_id)
if order is None:
return None
self._ensure_status(order.order_status, {"approved"}, "当前状态不允许生成发厂文案")
supplier = self.order_repository.get_supplier(session, payload["supplier_id"])
if supplier is None:
raise AppException(code=ErrorCode.NOT_FOUND, message="工厂不存在", status_code=404)
return {
"order_id": order.id,
"supplier_id": supplier.id,
"template_type": supplier.template_type or "default",
"text_content": f"订单 {order.order_no} 请安排生产与发货,客户:{order.customer_name}",
}
except SQLAlchemyError:
pass
order = demo_store.get_order(order_id)
if order is None:
return None
self._ensure_status(order["order_status"], {"approved"}, "当前状态不允许生成发厂文案")
return {
"order_id": order_id,
"supplier_id": payload["supplier_id"],
"template_type": "default",
"text_content": f"订单 {order['order_no']} 请安排生产与发货",
}
def confirm_supplier_text(self, order_id: int, payload: dict, session: Session | None = None) -> dict | None:
if session is not None:
try:
order = self.order_repository.get_order(session, order_id)
if order is None:
return None
self._ensure_status(order.order_status, {"approved"}, "当前状态不允许确认发厂")
supplier = self.order_repository.get_supplier(session, payload["supplier_id"])
if supplier is None:
raise AppException(code=ErrorCode.NOT_FOUND, message="工厂不存在", status_code=404)
# 当前阶段先把确认发厂和状态流转打通,文案内容后续再落库存档。
self.order_repository.update_supplier_text_confirm(session, order)
session.commit()
return {
"order_id": order.id,
"order_status": order.order_status,
"confirmed_at": order.supplier_text_confirmed_at.strftime("%Y-%m-%d %H:%M:%S")
if order.supplier_text_confirmed_at
else datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
except SQLAlchemyError:
session.rollback()
order = demo_store.get_order(order_id)
if order is None:
return None
self._ensure_status(order["order_status"], {"approved"}, "当前状态不允许确认发厂")
return {
"order_id": order_id,
"order_status": "pending_factory",
"confirmed_at": "2026-05-14 13:00:00",
}
def create_order(self, payload: dict, session: Session | None = None) -> dict:
if session is not None:
try:
if not payload["customer_name"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="客户姓名不能为空", status_code=400)
if not payload["customer_mobile"].strip():
raise AppException(code=ErrorCode.PARAM_ERROR, message="客户手机号不能为空", status_code=400)
customer = self.customer_repository.find_by_name_and_mobile(
session,
payload["customer_name"],
payload["customer_mobile"],
)
if customer is None:
customer = self.customer_repository.create_customer(
session,
{
"customer_name": payload["customer_name"],
"mobile": payload["customer_mobile"],
"address": payload.get("customer_address"),
"settlement_type": None,
"settlement_days": 0,
"customer_type": None,
"salesman_id": None,
"credit_limit": 0,
"remark": None,
},
)
items = payload["items"]
if not items:
raise AppException(code=ErrorCode.PARAM_ERROR, message="订单明细不能为空", status_code=400)
sale_total = sum(item["quantity"] * item["sale_price"] for item in items)
cost_total = sum(item["quantity"] * item["cost_price"] for item in items)
profit_total = (
sale_total
- cost_total
- payload["rebate_total"]
- payload["freight_total"]
- payload["tax_total"]
- payload["other_fee_total"]
)
profit_rate = round((profit_total / sale_total) * 100, 2) if sale_total else 0
order = self.order_repository.create_order(
session,
{
"order_no": f"SO{datetime.now().strftime('%Y%m%d%H%M%S')}",
"customer_id": customer.id,
"customer_name": payload["customer_name"],
"customer_mobile": payload["customer_mobile"],
"customer_address": payload.get("customer_address"),
"salesman_id": None,
"order_status": "draft",
"order_source": payload.get("order_source"),
"delivery_type": payload.get("delivery_type"),
"factory_id": payload.get("factory_id"),
"sale_price_total": sale_total,
"cost_price_total": cost_total,
"rebate_total": payload["rebate_total"],
"freight_total": payload["freight_total"],
"tax_total": payload["tax_total"],
"other_fee_total": payload["other_fee_total"],
"profit_total": profit_total,
"profit_rate": profit_rate,
"commission_amount": payload["commission_amount"],
"remark": payload.get("remark"),
},
items,
)
session.commit()
return {
"order_id": order.id,
"order_no": order.order_no,
"order_status": order.order_status,
"sale_price_total": round(sale_total, 2),
"cost_price_total": round(cost_total, 2),
"profit_total": round(profit_total, 2),
"profit_rate": profit_rate,
"commission_amount": payload["commission_amount"],
}
except SQLAlchemyError:
session.rollback()
return demo_store.create_order(payload)
def _ensure_status(self, current_status: str, allowed_statuses: set[str], message: str) -> None:
if current_status not in allowed_statuses:
raise AppException(
code=ErrorCode.INVALID_STATUS,
message=message,
status_code=400,
)
order_service = OrderService()