feat(huya): 增加协作单礼包字典与名称编辑
This commit is contained in:
@@ -36,6 +36,7 @@ from ..huya_defaults import (
|
||||
HUYA_DEFAULT_SID,
|
||||
)
|
||||
from ..models import (
|
||||
HuyaCollabGiftCatalog,
|
||||
HuyaCollabShop,
|
||||
HuyaConfig,
|
||||
HuyaExternalOrder,
|
||||
@@ -2579,6 +2580,7 @@ def export_cks(
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
SHOP_NAME_LIMIT = 64
|
||||
GIFT_NAME_LIMIT = 128
|
||||
|
||||
|
||||
def normalize_shop_name(value: Any) -> str:
|
||||
@@ -2831,6 +2833,257 @@ def list_shops(db: Session) -> list[str]:
|
||||
return sorted(shop.name for shop in list_shop_records(db))
|
||||
|
||||
|
||||
def _gift_audit_target(gift: HuyaCollabGiftCatalog) -> str:
|
||||
return f"huya_collab_gift:{gift.id}"
|
||||
|
||||
|
||||
def _touch_gift(gift: HuyaCollabGiftCatalog, current: User | None) -> None:
|
||||
gift.updated_by = current.id if current else None
|
||||
gift.updated_at = _now()
|
||||
|
||||
|
||||
def _clean_catalog_points(value: Any) -> int:
|
||||
points = _clean_required_points(value)
|
||||
if points is None:
|
||||
raise CollabError("礼包所需积分不能为空", code="gift_points_invalid")
|
||||
return points
|
||||
|
||||
|
||||
def _catalog_points_for_name(db: Session, name: str) -> int | None:
|
||||
clean = str(name or "").strip()
|
||||
if not clean:
|
||||
return None
|
||||
gift = (
|
||||
db.query(HuyaCollabGiftCatalog)
|
||||
.filter(HuyaCollabGiftCatalog.name == clean)
|
||||
.first()
|
||||
)
|
||||
return int(gift.points or 0) if gift is not None else None
|
||||
|
||||
|
||||
def serialize_gift(
|
||||
gift: HuyaCollabGiftCatalog, *, users: Mapping[int, str] | None = None
|
||||
) -> dict:
|
||||
names = users or {}
|
||||
|
||||
def name_of(value: int | None) -> str:
|
||||
return names.get(int(value), "") if value else ""
|
||||
|
||||
return {
|
||||
"id": gift.id,
|
||||
"name": gift.name or "",
|
||||
"points": int(gift.points or 0),
|
||||
"sort_order": int(gift.sort_order or 0),
|
||||
"is_active": bool(gift.is_active),
|
||||
"remark": gift.remark or "",
|
||||
"created_by_name": name_of(gift.created_by),
|
||||
"updated_by_name": name_of(gift.updated_by),
|
||||
"created_at": gift.created_at,
|
||||
"updated_at": gift.updated_at,
|
||||
}
|
||||
|
||||
|
||||
def list_gift_records(db: Session) -> list[HuyaCollabGiftCatalog]:
|
||||
"""全部礼包字典项(含停用;前端按 ``is_active`` 决定新建时是否可选)。"""
|
||||
return (
|
||||
db.query(HuyaCollabGiftCatalog)
|
||||
.order_by(
|
||||
HuyaCollabGiftCatalog.sort_order.asc(),
|
||||
HuyaCollabGiftCatalog.name.asc(),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
def get_gift(db: Session, gift_id: int) -> HuyaCollabGiftCatalog:
|
||||
gift = (
|
||||
db.query(HuyaCollabGiftCatalog)
|
||||
.filter(HuyaCollabGiftCatalog.id == gift_id)
|
||||
.first()
|
||||
)
|
||||
if gift is None:
|
||||
raise CollabError(
|
||||
"这个礼包不存在(可能已被别人删除)",
|
||||
status_code=404,
|
||||
code="gift_not_found",
|
||||
)
|
||||
return gift
|
||||
|
||||
|
||||
def _assert_gift_name_available(
|
||||
db: Session, name: str, *, exclude_id: int | None = None
|
||||
) -> None:
|
||||
query = db.query(HuyaCollabGiftCatalog).filter(HuyaCollabGiftCatalog.name == name)
|
||||
if exclude_id is not None:
|
||||
query = query.filter(HuyaCollabGiftCatalog.id != exclude_id)
|
||||
other = query.first()
|
||||
if other is not None:
|
||||
raise CollabError(
|
||||
f"礼包「{name}」已经在字典里了(第 {other.id} 行)",
|
||||
status_code=409,
|
||||
code="gift_name_conflict",
|
||||
)
|
||||
|
||||
|
||||
def create_gift(
|
||||
db: Session,
|
||||
current: User | None,
|
||||
*,
|
||||
name: Any,
|
||||
points: Any,
|
||||
sort_order: int = 0,
|
||||
remark: Any = "",
|
||||
commit: bool = True,
|
||||
) -> HuyaCollabGiftCatalog:
|
||||
clean_name = clean_text(name, field="gift_name", max_length=GIFT_NAME_LIMIT)
|
||||
if not clean_name:
|
||||
raise CollabError("礼包名不能为空", code="gift_name_required")
|
||||
_assert_gift_name_available(db, clean_name)
|
||||
gift = HuyaCollabGiftCatalog(
|
||||
name=clean_name,
|
||||
points=_clean_catalog_points(points),
|
||||
sort_order=int(sort_order or 0),
|
||||
is_active=True,
|
||||
remark=clean_text(remark, field="gift_remark", max_length=255),
|
||||
created_by=current.id if current else None,
|
||||
updated_by=current.id if current else None,
|
||||
)
|
||||
db.add(gift)
|
||||
db.flush()
|
||||
record_audit(
|
||||
db,
|
||||
current,
|
||||
action="huya:collab:gift_create",
|
||||
target=_gift_audit_target(gift),
|
||||
detail={
|
||||
"name": gift.name,
|
||||
"points": gift.points,
|
||||
"sort_order": gift.sort_order,
|
||||
},
|
||||
)
|
||||
if commit:
|
||||
db.commit()
|
||||
db.refresh(gift)
|
||||
return gift
|
||||
|
||||
|
||||
def update_gift(
|
||||
db: Session,
|
||||
current: User | None,
|
||||
gift: HuyaCollabGiftCatalog,
|
||||
payload: Mapping[str, Any],
|
||||
*,
|
||||
commit: bool = True,
|
||||
) -> dict:
|
||||
"""修改礼包字典,返回字段级 diff。
|
||||
|
||||
改名会级联所有历史订单上的 ``gift_name``;积分只改字典,不回填订单里
|
||||
已经形成的 ``required_points`` 快照。
|
||||
"""
|
||||
changes: dict[str, dict[str, Any]] = {}
|
||||
old_name = gift.name or ""
|
||||
renamed_orders = 0
|
||||
|
||||
if "name" in payload:
|
||||
new_name = clean_text(
|
||||
payload["name"], field="gift_name", max_length=GIFT_NAME_LIMIT
|
||||
)
|
||||
if not new_name:
|
||||
raise CollabError("礼包名不能为空", code="gift_name_required")
|
||||
if new_name != old_name:
|
||||
_assert_gift_name_available(db, new_name, exclude_id=gift.id)
|
||||
changes["name"] = {"old": old_name, "new": new_name}
|
||||
gift.name = new_name
|
||||
|
||||
if "points" in payload:
|
||||
value = _clean_catalog_points(payload["points"])
|
||||
if value != int(gift.points or 0):
|
||||
changes["points"] = {"old": int(gift.points or 0), "new": value}
|
||||
gift.points = value
|
||||
|
||||
if "sort_order" in payload:
|
||||
value = int(payload["sort_order"] or 0)
|
||||
if value != int(gift.sort_order or 0):
|
||||
changes["sort_order"] = {"old": int(gift.sort_order or 0), "new": value}
|
||||
gift.sort_order = value
|
||||
|
||||
if "is_active" in payload:
|
||||
value = bool(payload["is_active"])
|
||||
if value != bool(gift.is_active):
|
||||
changes["is_active"] = {"old": bool(gift.is_active), "new": value}
|
||||
gift.is_active = value
|
||||
|
||||
if "remark" in payload:
|
||||
value = clean_text(payload["remark"], field="gift_remark", max_length=255)
|
||||
if value != (gift.remark or ""):
|
||||
changes["remark"] = {"old": gift.remark or "", "new": value}
|
||||
gift.remark = value
|
||||
|
||||
if not changes:
|
||||
return {}
|
||||
|
||||
if "name" in changes:
|
||||
renamed_orders = (
|
||||
db.query(HuyaExternalOrder)
|
||||
.filter(HuyaExternalOrder.gift_name == old_name)
|
||||
.update(
|
||||
{HuyaExternalOrder.gift_name: changes["name"]["new"]},
|
||||
synchronize_session=False,
|
||||
)
|
||||
)
|
||||
|
||||
_touch_gift(gift, current)
|
||||
record_audit(
|
||||
db,
|
||||
current,
|
||||
action="huya:collab:gift_update",
|
||||
target=_gift_audit_target(gift),
|
||||
detail={"changes": changes, "renamed_orders": renamed_orders},
|
||||
)
|
||||
if commit:
|
||||
db.commit()
|
||||
db.refresh(gift)
|
||||
return changes
|
||||
|
||||
|
||||
def delete_gift(
|
||||
db: Session,
|
||||
current: User | None,
|
||||
gift: HuyaCollabGiftCatalog,
|
||||
*,
|
||||
commit: bool = True,
|
||||
) -> dict:
|
||||
"""删除未被订单引用的字典项;仍在使用时明确拒绝,避免历史名称失去标准口径。"""
|
||||
gift_name = gift.name or ""
|
||||
used = (
|
||||
db.query(func.count(HuyaExternalOrder.id))
|
||||
.filter(HuyaExternalOrder.gift_name == gift_name)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
if used:
|
||||
raise CollabError(
|
||||
f"还有 {used} 个协作单使用「{gift_name}」,请先停用或改名",
|
||||
code="gift_in_use",
|
||||
)
|
||||
snapshot = {
|
||||
"id": gift.id,
|
||||
"name": gift_name,
|
||||
"points": int(gift.points or 0),
|
||||
}
|
||||
record_audit(
|
||||
db,
|
||||
current,
|
||||
action="huya:collab:gift_delete",
|
||||
target=_gift_audit_target(gift),
|
||||
detail=snapshot,
|
||||
)
|
||||
db.delete(gift)
|
||||
if commit:
|
||||
db.commit()
|
||||
return snapshot
|
||||
|
||||
|
||||
def list_gift_names(db: Session) -> list[str]:
|
||||
"""台账里出现过的礼包/皮肤名(去重、去空;筛选下拉用)。"""
|
||||
rows = db.query(HuyaExternalOrder.gift_name).distinct().all()
|
||||
@@ -2935,13 +3188,20 @@ def create_order(
|
||||
# 草稿单不验 CK、不跑探针:先落一行占住订单号,资料到了在详情里补。
|
||||
require_verified = False
|
||||
|
||||
clean_gift_name = clean_text(
|
||||
gift_name, field="gift_name", max_length=GIFT_NAME_LIMIT
|
||||
)
|
||||
clean_required_points = _clean_required_points(required_points)
|
||||
if clean_gift_name and clean_required_points is None:
|
||||
clean_required_points = _catalog_points_for_name(db, clean_gift_name)
|
||||
|
||||
order = HuyaExternalOrder(
|
||||
order_no=order_no,
|
||||
shop=clean_text(shop, field="shop"),
|
||||
owner_id=_clean_owner_id(owner_id) if owner_id is not None else current.id,
|
||||
bind_douyu_account=clean_text(bind_douyu_account, field="bind_douyu_account"),
|
||||
gift_name=clean_text(gift_name, field="gift_name"),
|
||||
required_points=_clean_required_points(required_points),
|
||||
gift_name=clean_gift_name,
|
||||
required_points=clean_required_points,
|
||||
remark=clean_text(remark, field="remark"),
|
||||
bind_images=[],
|
||||
home_images=home_images_keys,
|
||||
@@ -3077,6 +3337,15 @@ def update_order(
|
||||
# 改成字典里没有的名字也允许(自定义),顺手登记,筛选下拉不会漏。
|
||||
ensure_shops_registered(db, current, [changes["shop"]["new"]])
|
||||
|
||||
if "gift_name" in changes and "required_points" not in payload:
|
||||
catalog_points = _catalog_points_for_name(db, changes["gift_name"]["new"])
|
||||
if catalog_points is not None and catalog_points != order.required_points:
|
||||
changes["required_points"] = {
|
||||
"old": order.required_points,
|
||||
"new": catalog_points,
|
||||
}
|
||||
order.required_points = catalog_points
|
||||
|
||||
# 先记「人工改了什么」,再跑自动状态迁移:否则时间线会出现
|
||||
# status_auto 排在 update 前面,像状态是被后一次操作改的。
|
||||
record_audit(
|
||||
|
||||
Reference in New Issue
Block a user