Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab034d3c15 | |||
| 58d609e5d2 | |||
| 973c440a8a |
@@ -63,6 +63,12 @@ def _device_marketing_name(model: str | None) -> str | None:
|
|||||||
return _DEVICE_MARKETING_NAMES.get(model.strip().upper())
|
return _DEVICE_MARKETING_NAMES.get(model.strip().upper())
|
||||||
|
|
||||||
|
|
||||||
|
def _attach_comparison_device_details(items: list[ComparisonRecord]) -> None:
|
||||||
|
"""给比价记录补充可读机型名,同时保留原始设备编码。"""
|
||||||
|
for item in items:
|
||||||
|
item.device_model_name = _device_marketing_name(item.device_model)
|
||||||
|
|
||||||
|
|
||||||
def _attach_feedback_device_details(db: Session, feedbacks: list[Feedback]) -> None:
|
def _attach_feedback_device_details(db: Session, feedbacks: list[Feedback]) -> None:
|
||||||
"""按同一用户、同一设备编码及提交时间补齐厂商和 ROM 大版本。"""
|
"""按同一用户、同一设备编码及提交时间补齐厂商和 ROM 大版本。"""
|
||||||
candidates = [
|
candidates = [
|
||||||
@@ -344,6 +350,7 @@ def list_comparison_records(
|
|||||||
limit=limit, cursor=cursor,
|
limit=limit, cursor=cursor,
|
||||||
)
|
)
|
||||||
_attach_user_info(db, items)
|
_attach_user_info(db, items)
|
||||||
|
_attach_comparison_device_details(items)
|
||||||
# 「本次比价看广告的预估收益」:按本页 trace_id 一次性聚合(同 _attach_user_info 逐页范式)。
|
# 「本次比价看广告的预估收益」:按本页 trace_id 一次性聚合(同 _attach_user_info 逐页范式)。
|
||||||
# ad_revenue_yuan 非 ORM 列,仅瞬态挂实例上供 AdminComparisonListItem(from_attributes)读出。
|
# ad_revenue_yuan 非 ORM 列,仅瞬态挂实例上供 AdminComparisonListItem(from_attributes)读出。
|
||||||
rev = ad_ecpm.revenue_yuan_by_trace(db, [it.trace_id for it in items])
|
rev = ad_ecpm.revenue_yuan_by_trace(db, [it.trace_id for it in items])
|
||||||
@@ -490,6 +497,7 @@ def get_comparison_record(db: Session, record_id: int) -> ComparisonRecord | Non
|
|||||||
rec = db.get(ComparisonRecord, record_id)
|
rec = db.get(ComparisonRecord, record_id)
|
||||||
if rec is not None:
|
if rec is not None:
|
||||||
_attach_user_info(db, [rec])
|
_attach_user_info(db, [rec])
|
||||||
|
_attach_comparison_device_details([rec])
|
||||||
return rec
|
return rec
|
||||||
|
|
||||||
|
|
||||||
@@ -948,7 +956,7 @@ def get_withdraw_by_out_bill_no(db: Session, out_bill_no: str) -> WithdrawOrder
|
|||||||
|
|
||||||
|
|
||||||
def withdraw_summary(db: Session, *, source: str | None = None) -> dict:
|
def withdraw_summary(db: Session, *, source: str | None = None) -> dict:
|
||||||
"""提现审核台顶部统计。金额单位:分。"""
|
"""提现审核台顶部统计。金额单位:分;口径与列表的 source 筛选一致。"""
|
||||||
summary_stmt = select(
|
summary_stmt = select(
|
||||||
WithdrawOrder.status,
|
WithdrawOrder.status,
|
||||||
func.count(WithdrawOrder.id),
|
func.count(WithdrawOrder.id),
|
||||||
@@ -977,21 +985,29 @@ def withdraw_summary(db: Session, *, source: str | None = None) -> dict:
|
|||||||
stmt = stmt.where(WithdrawOrder.source == source)
|
stmt = stmt.where(WithdrawOrder.source == source)
|
||||||
return db.execute(stmt).scalar_one()
|
return db.execute(stmt).scalar_one()
|
||||||
|
|
||||||
today_amount_stmt = select(
|
today_success_amount_stmt = select(
|
||||||
func.coalesce(func.sum(WithdrawOrder.amount_cents), 0)
|
func.coalesce(func.sum(WithdrawOrder.amount_cents), 0)
|
||||||
).where(
|
).where(
|
||||||
WithdrawOrder.status == "success",
|
WithdrawOrder.status == "success",
|
||||||
WithdrawOrder.updated_at >= today_start,
|
WithdrawOrder.updated_at >= today_start,
|
||||||
)
|
)
|
||||||
if source:
|
if source:
|
||||||
today_amount_stmt = today_amount_stmt.where(WithdrawOrder.source == source)
|
today_success_amount_stmt = today_success_amount_stmt.where(
|
||||||
today_success_amount = db.execute(today_amount_stmt).scalar_one()
|
WithdrawOrder.source == source
|
||||||
|
)
|
||||||
|
today_success_amount = db.execute(today_success_amount_stmt).scalar_one()
|
||||||
|
|
||||||
|
def _status_count(status: str) -> int:
|
||||||
|
return by_status.get(status, {}).get("count", 0)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"reviewing_count": by_status.get("reviewing", {}).get("count", 0),
|
"reviewing_count": _status_count("reviewing"),
|
||||||
"reviewing_amount_cents": by_status.get("reviewing", {}).get("amount_cents", 0),
|
"reviewing_amount_cents": by_status.get("reviewing", {}).get("amount_cents", 0),
|
||||||
"pending_count": by_status.get("pending", {}).get("count", 0),
|
"pending_count": _status_count("pending"),
|
||||||
"failed_count": by_status.get("failed", {}).get("count", 0),
|
"success_count": _status_count("success"),
|
||||||
|
"rejected_count": _status_count("rejected"),
|
||||||
|
"failed_count": _status_count("failed"),
|
||||||
|
"total_count": sum(item["count"] for item in by_status.values()),
|
||||||
"today_success_count": _today_count("success"),
|
"today_success_count": _today_count("success"),
|
||||||
"today_success_amount_cents": int(today_success_amount),
|
"today_success_amount_cents": int(today_success_amount),
|
||||||
"today_rejected_count": _today_count("rejected"),
|
"today_rejected_count": _today_count("rejected"),
|
||||||
|
|||||||
@@ -39,8 +39,10 @@ class AdminComparisonListItem(BaseModel):
|
|||||||
# 本次比价 LLM 总成本(元,按当时价冻结);旧记录/未回填为 None → 前端「成本」列回退估算。见 services/llm_cost.py。
|
# 本次比价 LLM 总成本(元,按当时价冻结);旧记录/未回填为 None → 前端「成本」列回退估算。见 services/llm_cost.py。
|
||||||
llm_cost_yuan: float | None = None
|
llm_cost_yuan: float | None = None
|
||||||
device_model: str | None = None
|
device_model: str | None = None
|
||||||
|
device_model_name: str | None = None
|
||||||
rom_vendor: str | None = None
|
rom_vendor: str | None = None
|
||||||
rom_name: str | None = None
|
rom_name: str | None = None
|
||||||
|
rom_version: int | None = None
|
||||||
android_version: str | None = None
|
android_version: str | None = None
|
||||||
app_version: str | None = None
|
app_version: str | None = None
|
||||||
ad_revenue_yuan: float = 0.0 # 本次比价看的信息流广告预估收益(元),queries 瞬态挂 ORM 实例上
|
ad_revenue_yuan: float = 0.0 # 本次比价看的信息流广告预估收益(元),queries 瞬态挂 ORM 实例上
|
||||||
@@ -84,7 +86,6 @@ class AdminComparisonDetail(AdminComparisonListItem):
|
|||||||
skipped_dish_names: list = []
|
skipped_dish_names: list = []
|
||||||
# 全量环境
|
# 全量环境
|
||||||
device_manufacturer: str | None = None
|
device_manufacturer: str | None = None
|
||||||
rom_version: int | None = None
|
|
||||||
android_sdk: int | None = None
|
android_sdk: int | None = None
|
||||||
app_version_code: int | None = None
|
app_version_code: int | None = None
|
||||||
source_app_version: str | None = None
|
source_app_version: str | None = None
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ class WithdrawSummaryOut(BaseModel):
|
|||||||
reviewing_count: int
|
reviewing_count: int
|
||||||
reviewing_amount_cents: int
|
reviewing_amount_cents: int
|
||||||
pending_count: int
|
pending_count: int
|
||||||
|
success_count: int
|
||||||
|
rejected_count: int
|
||||||
failed_count: int
|
failed_count: int
|
||||||
|
total_count: int
|
||||||
today_success_count: int
|
today_success_count: int
|
||||||
today_success_amount_cents: int
|
today_success_amount_cents: int
|
||||||
today_rejected_count: int
|
today_rejected_count: int
|
||||||
|
|||||||
@@ -504,6 +504,142 @@ def test_invite_withdraw_detail_contains_invitee_first_actions(
|
|||||||
assert filtered.json()["invite_overview"]["invite_total"] == 0
|
assert filtered.json()["invite_overview"]["invite_total"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_withdraw_summary_counts_match_status_lists_by_source(
|
||||||
|
admin_client: TestClient, admin_token: str
|
||||||
|
) -> None:
|
||||||
|
"""每个审核页签的汇总数必须等于相同 source、status 的列表总数。"""
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
uid = user_repo.upsert_user_for_login(
|
||||||
|
db, phone="13800009003", register_channel="sms"
|
||||||
|
).id
|
||||||
|
for index, status in enumerate(
|
||||||
|
("reviewing", "pending", "success", "rejected", "failed"), start=1
|
||||||
|
):
|
||||||
|
db.add(
|
||||||
|
WithdrawOrder(
|
||||||
|
user_id=uid,
|
||||||
|
out_bill_no=f"summary{uid}invite{index}",
|
||||||
|
amount_cents=index * 10,
|
||||||
|
status=status,
|
||||||
|
source="invite_cash",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
db.add(
|
||||||
|
WithdrawOrder(
|
||||||
|
user_id=uid,
|
||||||
|
out_bill_no=f"summary{uid}coinfailed",
|
||||||
|
amount_cents=99,
|
||||||
|
status="failed",
|
||||||
|
source="coin_cash",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
status_fields = {
|
||||||
|
"reviewing": "reviewing_count",
|
||||||
|
"pending": "pending_count",
|
||||||
|
"success": "success_count",
|
||||||
|
"rejected": "rejected_count",
|
||||||
|
"failed": "failed_count",
|
||||||
|
}
|
||||||
|
for source in ("coin_cash", "invite_cash"):
|
||||||
|
summary_response = admin_client.get(
|
||||||
|
"/admin/api/withdraws/summary",
|
||||||
|
params={"source": source},
|
||||||
|
headers=_auth(admin_token),
|
||||||
|
)
|
||||||
|
assert summary_response.status_code == 200, summary_response.text
|
||||||
|
summary = summary_response.json()
|
||||||
|
|
||||||
|
for status, field in status_fields.items():
|
||||||
|
list_response = admin_client.get(
|
||||||
|
"/admin/api/withdraws",
|
||||||
|
params={"source": source, "status": status, "limit": 1},
|
||||||
|
headers=_auth(admin_token),
|
||||||
|
)
|
||||||
|
assert list_response.status_code == 200, list_response.text
|
||||||
|
assert summary[field] == list_response.json()["total"]
|
||||||
|
|
||||||
|
reviewing_response = admin_client.get(
|
||||||
|
"/admin/api/withdraws",
|
||||||
|
params={"source": source, "status": "reviewing", "limit": 100},
|
||||||
|
headers=_auth(admin_token),
|
||||||
|
)
|
||||||
|
assert reviewing_response.status_code == 200, reviewing_response.text
|
||||||
|
reviewing_body = reviewing_response.json()
|
||||||
|
assert reviewing_body["total"] == len(reviewing_body["items"])
|
||||||
|
assert summary["reviewing_amount_cents"] == sum(
|
||||||
|
item["amount_cents"] for item in reviewing_body["items"]
|
||||||
|
)
|
||||||
|
|
||||||
|
all_response = admin_client.get(
|
||||||
|
"/admin/api/withdraws",
|
||||||
|
params={"source": source, "limit": 1},
|
||||||
|
headers=_auth(admin_token),
|
||||||
|
)
|
||||||
|
assert all_response.status_code == 200, all_response.text
|
||||||
|
assert summary["total_count"] == all_response.json()["total"]
|
||||||
|
assert summary["total_count"] == sum(
|
||||||
|
summary[field] for field in status_fields.values()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert admin_client.get(
|
||||||
|
"/admin/api/withdraws/summary",
|
||||||
|
params={"source": "unknown"},
|
||||||
|
headers=_auth(admin_token),
|
||||||
|
).status_code == 422
|
||||||
|
|
||||||
|
|
||||||
|
def test_comparison_records_show_readable_device_and_rom_version(
|
||||||
|
admin_client: TestClient, admin_token: str
|
||||||
|
) -> None:
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
user = user_repo.upsert_user_for_login(
|
||||||
|
db, phone="13800009041", register_channel="sms"
|
||||||
|
)
|
||||||
|
record = ComparisonRecord(
|
||||||
|
user_id=user.id,
|
||||||
|
trace_id="comparison-readable-device",
|
||||||
|
status="success",
|
||||||
|
device_model="V2166BA",
|
||||||
|
device_manufacturer="vivo",
|
||||||
|
rom_vendor="vivo",
|
||||||
|
rom_name="OriginOS",
|
||||||
|
rom_version=4,
|
||||||
|
android_version="14",
|
||||||
|
)
|
||||||
|
db.add(record)
|
||||||
|
db.commit()
|
||||||
|
record_id = record.id
|
||||||
|
user_id = user.id
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
response = admin_client.get(
|
||||||
|
"/admin/api/comparison-records",
|
||||||
|
params={"user_id": user_id},
|
||||||
|
headers=_auth(admin_token),
|
||||||
|
)
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
item = response.json()["items"][0]
|
||||||
|
assert item["device_model"] == "V2166BA"
|
||||||
|
assert item["device_model_name"] == "vivo Y77e"
|
||||||
|
assert item["rom_name"] == "OriginOS"
|
||||||
|
assert item["rom_version"] == 4
|
||||||
|
|
||||||
|
detail = admin_client.get(
|
||||||
|
f"/admin/api/comparison-records/{record_id}",
|
||||||
|
headers=_auth(admin_token),
|
||||||
|
)
|
||||||
|
assert detail.status_code == 200, detail.text
|
||||||
|
assert detail.json()["device_model_name"] == "vivo Y77e"
|
||||||
|
assert detail.json()["rom_version"] == 4
|
||||||
|
|
||||||
|
|
||||||
def test_ad_coin_audit_full_count_truncate_and_only_mismatch(
|
def test_ad_coin_audit_full_count_truncate_and_only_mismatch(
|
||||||
admin_client: TestClient, admin_token: str
|
admin_client: TestClient, admin_token: str
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user