feat(huya): 协作台账支持待补资料一键建单与表单暂存

This commit is contained in:
yml2213
2026-10-17 14:21:36 +08:00
parent cad06ae789
commit fcc2fbdc09
8 changed files with 401 additions and 50 deletions
+4
View File
@@ -267,6 +267,9 @@ def create_external_order(
):
"""新建一行(可同时贴一份外部 CK)。订单号唯一,重复会 409。
``draft=true``:先建单后补资料 —— 订单号 + 店铺即可,状态进「待补资料」,
CK / 礼包名 / 主页截图稍后在详情里补(不跑探针)。
``require_verified``(默认 true):人工新建表单的严格模式 —— 订单编号 / 店铺 / CK /
礼包名 / 主页截图必填,且 CK 必须探针通并读到绑定角色,否则 400
(粘贴导入走另一个接口,不受影响)。
@@ -284,6 +287,7 @@ def create_external_order(
home_images=req.home_images,
remark=req.remark,
require_verified=req.require_verified,
draft=req.draft,
)
except collab.CollabError as exc:
raise _http_error(exc) from exc
+4
View File
@@ -1170,11 +1170,15 @@ class HuyaExternalOrderCreate(BaseModel):
``require_verified``(默认开):人工新建表单的严格模式 —— 订单编号 / 店铺 / CK /
礼包名 / **主页截图**必填,且 CK 必须探针通并读到绑定角色;粘贴导入不走这条路。
``draft``(默认关):先建单后补资料 —— 只登记订单号 + 店铺,状态进「待补资料」,
CK / 礼包名 / 主页截图都可以稍后在详情里补(2026-10-17 用户需求)。
"""
order_no: str = Field(..., min_length=1, max_length=64)
shop: str = Field("", max_length=64)
require_verified: bool = True
draft: bool = False
owner_id: int | None = None
ck_text: str = ""
bind_douyu_account: str = Field("", max_length=128)
+51 -6
View File
@@ -51,6 +51,9 @@ from .huya_service import build_web_cookie_from_raw, parse_huya_cookie_line
# 业务状态机(一单卡在哪一步)
# --------------------------------------------------------------------------
# 「待补资料」是流程链的起点:只登记订单号 + 店铺,CK / 礼包 / 主页截图等客户资料
# 到了再补(2026-10-17 用户需求)。资料齐全后才能进「待处理」。
STATUS_INCOMPLETE = "incomplete"
STATUS_PENDING = "pending"
STATUS_BOUND = "bound"
STATUS_RECHARGED = "recharged"
@@ -60,6 +63,7 @@ STATUS_FAILED = "failed"
STATUS_VOID = "void"
ORDER_STATUSES: tuple[str, ...] = (
STATUS_INCOMPLETE,
STATUS_PENDING,
STATUS_BOUND,
STATUS_RECHARGED,
@@ -70,6 +74,7 @@ ORDER_STATUSES: tuple[str, ...] = (
)
ORDER_STATUS_LABELS = {
STATUS_INCOMPLETE: "待补资料",
STATUS_PENDING: "待处理",
# 人工核对过「探针读到的绑定角色」与「主页截图」一致,才允许进这一步(见 _assert_can_enter_bound)
STATUS_BOUND: "已确认绑定",
@@ -84,6 +89,7 @@ ORDER_STATUS_LABELS = {
# 流程链(时间戳同步与「能不能向前走」都只看这条链);failed / void 是**旁路**终态。
STATUS_FLOW: tuple[str, ...] = (
STATUS_INCOMPLETE,
STATUS_PENDING,
STATUS_BOUND,
STATUS_RECHARGED,
@@ -93,6 +99,7 @@ STATUS_FLOW: tuple[str, ...] = (
# 「向前一步」的下一站;done 是终点,failed / void 是任意态可达的旁路,都不在这条链上。
NEXT_STATUS = {
STATUS_INCOMPLETE: STATUS_PENDING,
STATUS_PENDING: STATUS_BOUND,
STATUS_BOUND: STATUS_RECHARGED,
STATUS_RECHARGED: STATUS_SCRIPTED,
@@ -101,11 +108,12 @@ NEXT_STATUS = {
# 阶段序号:用来同步时间戳(回退时要清掉后面阶段的时间戳,否则状态与时间戳互相矛盾)。
STAGE_INDEX = {
STATUS_PENDING: 0,
STATUS_BOUND: 1,
STATUS_RECHARGED: 2,
STATUS_SCRIPTED: 3,
STATUS_DONE: 4,
STATUS_INCOMPLETE: 0,
STATUS_PENDING: 1,
STATUS_BOUND: 2,
STATUS_RECHARGED: 3,
STATUS_SCRIPTED: 4,
STATUS_DONE: 5,
}
STATUS_TIMESTAMP_FIELDS = {
@@ -451,6 +459,26 @@ def _assert_can_enter_bound(order: HuyaExternalOrder) -> None:
)
def _assert_can_leave_incomplete(order: HuyaExternalOrder) -> None:
"""离开「待补资料」的前置校验:客户资料必须齐全。
只在 ``incomplete`` 这个起点用:CK / 礼包名 / 主页截图至少各一份,
否则「待处理」只是个空壳(后面谁都处理不了)。作废 / 判失败是旁路,不拦。
"""
missing: list[str] = []
if not str(order.cookie or "").strip():
missing.append("外部 CK")
if not str(order.gift_name or "").strip():
missing.append("礼包名")
if not images_of(order, "home_images"):
missing.append("主页截图(至少 1 张)")
if missing:
raise CollabError(
f"「{ORDER_STATUS_LABELS[STATUS_INCOMPLETE]}」还不能往前改,缺少:{'、'.join(missing)}",
code="incomplete_precondition",
)
def _sync_stage_timestamps(
order: HuyaExternalOrder, target: str, now: datetime
) -> None:
@@ -520,6 +548,13 @@ def set_status(
code="status_from_void",
)
if from_status == STATUS_INCOMPLETE and target not in (
STATUS_VOID,
STATUS_FAILED,
):
# 「待补资料」是起点:客户资料没齐之前不许往前,否则下一个环节无从下手。
_assert_can_leave_incomplete(order)
if target == STATUS_BOUND:
_assert_can_enter_bound(order)
@@ -2045,6 +2080,7 @@ def create_order(
home_images: Any = None,
remark: str = "",
require_verified: bool = False,
draft: bool = False,
client: Any | None = None,
commit: bool = True,
) -> HuyaExternalOrder:
@@ -2053,6 +2089,10 @@ def create_order(
**负责人不填就是当前登录用户**(创建人)—— 新建时不用选,谁建的单归谁;
要转给别人在列表里点一下改负责人即可(负责人不是权限边界,见设计文档 §7.1)。
``draft=True``(**先建单后补资料**):只要求订单号,店铺也允许先空着;
状态直接进「待补资料」,CK / 礼包名 / 主页截图都可以等客户资料到了再补。
这条路径不跑探针、不做严格校验(2026-10-17 用户需求)。
``require_verified=True``(**人工新建表单用**,粘贴导入不传)时按"人工必填项"严格校验:
**订单编号 / 店铺 / CK / 礼包名 / 主页截图**都不许空,且 CK 必须探针通、
**读到绑定角色**才让建;读不到角色直接 400 —— 这条在服务层做,前端只是体验(AGENTS §2)。
@@ -2065,6 +2105,10 @@ def create_order(
_assert_order_no_available(db, order_no)
home_images_keys = normalize_images(home_images, field="home_images")
if draft:
# 草稿单不验 CK、不跑探针:先落一行占住订单号,资料到了在详情里补。
require_verified = False
order = HuyaExternalOrder(
order_no=order_no,
shop=clean_text(shop, field="shop"),
@@ -2074,7 +2118,7 @@ def create_order(
remark=clean_text(remark, field="remark"),
bind_images=[],
home_images=home_images_keys,
status=STATUS_PENDING,
status=STATUS_INCOMPLETE if draft else STATUS_PENDING,
ck_status=CK_UNVERIFIED,
created_by=current.id,
updated_by=current.id,
@@ -2140,6 +2184,7 @@ def create_order(
"gift_name": order.gift_name,
"bind_game_role": order.bind_game_role or "",
"home_images": len(order.home_images or []),
"draft": bool(draft),
# 这个虎牙号还挂在哪些单上(允许同一份 CK 服务多单,留个痕)
"uid_reused_in": uid_reused,
},