2026-06-14 05:26:27 +08:00
|
|
|
|
"""用户端 Web 自助订单创建流程(T12.3)。"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from typing import Literal, Optional
|
|
|
|
|
|
|
|
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
|
|
|
|
|
|
|
|
from data.orders.dao import OrdersDAO
|
|
|
|
|
|
from data.orders.models import Order, generate_order_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ServiceVersion = Literal["audit", "basic", "standard", "premium"]
|
|
|
|
|
|
_SERVICE_PRICES: dict[ServiceVersion, int] = {
|
|
|
|
|
|
"audit": 4900,
|
|
|
|
|
|
"basic": 4900,
|
|
|
|
|
|
"standard": 9900,
|
|
|
|
|
|
"premium": 19900,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-25 09:26:31 +08:00
|
|
|
|
_SUPPORTED_PUBLIC_PROVINCES = {
|
|
|
|
|
|
"北京",
|
|
|
|
|
|
"天津",
|
|
|
|
|
|
"上海",
|
|
|
|
|
|
"重庆",
|
|
|
|
|
|
"河北",
|
|
|
|
|
|
"河南",
|
|
|
|
|
|
"山东",
|
|
|
|
|
|
"山西",
|
|
|
|
|
|
"陕西",
|
|
|
|
|
|
"辽宁",
|
|
|
|
|
|
"吉林",
|
|
|
|
|
|
"黑龙江",
|
|
|
|
|
|
"江苏",
|
|
|
|
|
|
"浙江",
|
|
|
|
|
|
"安徽",
|
|
|
|
|
|
"福建",
|
|
|
|
|
|
"江西",
|
|
|
|
|
|
"湖北",
|
|
|
|
|
|
"湖南",
|
|
|
|
|
|
"广东",
|
|
|
|
|
|
"海南",
|
|
|
|
|
|
"四川",
|
|
|
|
|
|
"贵州",
|
|
|
|
|
|
"云南",
|
|
|
|
|
|
"甘肃",
|
|
|
|
|
|
"青海",
|
|
|
|
|
|
"新疆",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-14 05:26:27 +08:00
|
|
|
|
def service_price_for(service_version: ServiceVersion) -> int:
|
|
|
|
|
|
return _SERVICE_PRICES[service_version]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PublicOrderCreate(BaseModel):
|
|
|
|
|
|
service_version: ServiceVersion
|
|
|
|
|
|
amount_cents: int = Field(ge=0)
|
2026-06-16 12:16:45 +08:00
|
|
|
|
customer_name: Optional[str] = None
|
2026-06-17 09:01:43 +08:00
|
|
|
|
customer_phone: Optional[str] = Field(default=None, min_length=1)
|
2026-06-14 05:26:27 +08:00
|
|
|
|
customer_wechat: Optional[str] = None
|
2026-06-15 11:39:31 +08:00
|
|
|
|
customer_email: Optional[str] = None
|
2026-06-16 12:16:45 +08:00
|
|
|
|
candidate_name: str = Field(min_length=1)
|
|
|
|
|
|
candidate_province: Optional[str] = None
|
2026-06-14 05:26:27 +08:00
|
|
|
|
notes: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
@model_validator(mode="after")
|
2026-06-16 12:16:45 +08:00
|
|
|
|
def _validate_minimal_checkout(self) -> "PublicOrderCreate":
|
2026-06-14 05:26:27 +08:00
|
|
|
|
expected_amount = service_price_for(self.service_version)
|
|
|
|
|
|
if self.amount_cents != expected_amount:
|
|
|
|
|
|
raise ValueError("amount_cents 与套餐价格不一致")
|
2026-06-17 09:01:43 +08:00
|
|
|
|
if not self.customer_phone and not self.customer_wechat:
|
|
|
|
|
|
raise ValueError("customer_phone / customer_wechat 至少填写一个")
|
2026-06-25 09:26:31 +08:00
|
|
|
|
if self.candidate_province and self.candidate_province not in _SUPPORTED_PUBLIC_PROVINCES:
|
|
|
|
|
|
raise ValueError("candidate_province 当前暂不支持")
|
2026-06-14 05:26:27 +08:00
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-25 09:26:31 +08:00
|
|
|
|
|
2026-06-14 05:26:27 +08:00
|
|
|
|
class PublicOrderCreated(BaseModel):
|
|
|
|
|
|
order_id: str
|
|
|
|
|
|
source: str
|
|
|
|
|
|
status: str
|
|
|
|
|
|
service_version: str
|
|
|
|
|
|
amount_cents: int
|
|
|
|
|
|
next_step: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_public_order(dao: OrdersDAO, request: PublicOrderCreate) -> Order:
|
|
|
|
|
|
order = Order(
|
|
|
|
|
|
id=generate_order_id(),
|
|
|
|
|
|
source="web",
|
|
|
|
|
|
service_version=request.service_version,
|
|
|
|
|
|
amount_cents=service_price_for(request.service_version),
|
|
|
|
|
|
status="pending",
|
|
|
|
|
|
customer_name=request.customer_name,
|
|
|
|
|
|
customer_phone=request.customer_phone,
|
|
|
|
|
|
customer_wechat=request.customer_wechat,
|
2026-06-15 11:39:31 +08:00
|
|
|
|
customer_email=request.customer_email,
|
2026-06-14 05:26:27 +08:00
|
|
|
|
candidate_name=request.candidate_name,
|
|
|
|
|
|
candidate_province=request.candidate_province,
|
|
|
|
|
|
notes=request.notes,
|
|
|
|
|
|
tags=["web-self-service", f"package:{request.service_version}"],
|
|
|
|
|
|
)
|
|
|
|
|
|
return dao.create(order, actor="public_web", reason="public_create")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def to_created_payload(order: Order) -> PublicOrderCreated:
|
|
|
|
|
|
return PublicOrderCreated(
|
|
|
|
|
|
order_id=order.id,
|
|
|
|
|
|
source=order.source,
|
|
|
|
|
|
status=order.status,
|
|
|
|
|
|
service_version=order.service_version,
|
|
|
|
|
|
amount_cents=order.amount_cents,
|
|
|
|
|
|
next_step="payment",
|
|
|
|
|
|
)
|