e1bd0e3ef7
改了什么:新增新版大盘日期窗口聚合、广告收益拆分、比价耗时字段、美团 CPS 拉单入库与大盘展示,并同步反馈审核和邀请奖励下线口径。 验证:python -m pytest tests/test_admin_read.py tests/test_admin_write.py tests/test_compare_record.py tests/test_invite.py tests/test_feedback.py -q。
131 lines
4.1 KiB
Python
131 lines
4.1 KiB
Python
"""Admin CPS 订单对账:调用美团 query_order 并 upsert 到 cps_order。"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from decimal import Decimal, InvalidOperation
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.integrations import meituan
|
|
from app.models.cps_order import CpsOrder
|
|
|
|
|
|
def _yuan_to_cents(v: object) -> int | None:
|
|
if v is None:
|
|
return None
|
|
s = str(v).strip()
|
|
if not s or s.lower() == "null":
|
|
return None
|
|
try:
|
|
return int((Decimal(s) * 100).to_integral_value())
|
|
except (InvalidOperation, ValueError):
|
|
return None
|
|
|
|
|
|
def _ts_to_dt(ts: object) -> datetime | None:
|
|
if ts is None:
|
|
return None
|
|
s = str(ts).strip()
|
|
if not s or s.lower() == "null":
|
|
return None
|
|
try:
|
|
return datetime.fromtimestamp(int(s), tz=timezone.utc)
|
|
except (OSError, TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def _map_order_fields(row: dict) -> dict:
|
|
product_name = row.get("productName")
|
|
if product_name and len(product_name) > 500:
|
|
product_name = product_name[:500]
|
|
return {
|
|
"sid": row.get("sid") or None,
|
|
"act_id": str(row["actId"]) if row.get("actId") is not None else None,
|
|
"biz_line": row.get("businessLine"),
|
|
"trade_type": row.get("tradeType"),
|
|
"pay_price_cents": _yuan_to_cents(row.get("payPrice")),
|
|
"commission_cents": _yuan_to_cents(row.get("profit") or row.get("cpaProfit")),
|
|
"commission_rate": (
|
|
str(row["commissionRate"]) if row.get("commissionRate") is not None else None
|
|
),
|
|
"refund_price_cents": _yuan_to_cents(row.get("refundPrice")),
|
|
"refund_profit_cents": _yuan_to_cents(row.get("refundProfit") or row.get("cpaRefundProfit")),
|
|
"mt_status": str(row["status"]) if row.get("status") is not None else None,
|
|
"invalid_reason": row.get("invalidReason") or None,
|
|
"product_name": product_name or None,
|
|
"pay_time": _ts_to_dt(row.get("payTime")),
|
|
"mt_update_time": _ts_to_dt(row.get("updateTime")),
|
|
"raw": row,
|
|
}
|
|
|
|
|
|
def reconcile_orders(
|
|
db: Session,
|
|
*,
|
|
start_time: int,
|
|
end_time: int,
|
|
query_time_type: int = 1,
|
|
sid: str | None = None,
|
|
platform: int | None = None,
|
|
business_line: list[int] | None = None,
|
|
trade_type: int | None = 1,
|
|
search_type: int = 2,
|
|
max_pages: int = 200,
|
|
) -> dict:
|
|
fetched = inserted = updated = pages = 0
|
|
page = 1
|
|
scroll_id: str | None = None
|
|
seen_scroll_ids: set[str] = set()
|
|
|
|
while pages < max_pages:
|
|
resp = meituan.query_order(
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
query_time_type=query_time_type,
|
|
sid=sid,
|
|
platform=platform,
|
|
business_line=business_line,
|
|
trade_type=trade_type,
|
|
search_type=search_type,
|
|
scroll_id=scroll_id,
|
|
page=page,
|
|
limit=100,
|
|
)
|
|
data = resp.get("data") or {}
|
|
rows = data.get("dataList") or []
|
|
if not rows:
|
|
break
|
|
|
|
pages += 1
|
|
for row in rows:
|
|
order_id = str(row.get("orderId") or "").strip()
|
|
if not order_id:
|
|
continue
|
|
fetched += 1
|
|
fields = _map_order_fields(row)
|
|
existing = db.execute(
|
|
select(CpsOrder).where(CpsOrder.order_id == order_id)
|
|
).scalar_one_or_none()
|
|
if existing is None:
|
|
db.add(CpsOrder(order_id=order_id, **fields))
|
|
inserted += 1
|
|
else:
|
|
for key, value in fields.items():
|
|
setattr(existing, key, value)
|
|
updated += 1
|
|
|
|
if search_type == 2:
|
|
next_scroll_id = data.get("scrollId")
|
|
if not next_scroll_id or next_scroll_id in seen_scroll_ids or len(rows) < 100:
|
|
break
|
|
seen_scroll_ids.add(next_scroll_id)
|
|
scroll_id = next_scroll_id
|
|
else:
|
|
if len(rows) < 100:
|
|
break
|
|
page += 1
|
|
|
|
db.commit()
|
|
return {"fetched": fetched, "inserted": inserted, "updated": updated, "pages": pages}
|