diff --git a/app/admin/repositories/queries.py b/app/admin/repositories/queries.py index 6852638..41f113c 100644 --- a/app/admin/repositories/queries.py +++ b/app/admin/repositories/queries.py @@ -63,6 +63,34 @@ def _device_marketing_name(model: str | None) -> str | None: return _DEVICE_MARKETING_NAMES.get(model.strip().upper()) +def _attach_comparison_order_status(db: Session, items: list[ComparisonRecord]) -> None: + """按 C 端既有口径给比价记录批量补充是否真实下单。""" + user_ids = {item.user_id for item in items if item.user_id is not None} + shop_names = {item.store_name for item in items if item.store_name} + ordered_pairs: set[tuple[int, str]] = set() + if user_ids and shop_names: + rows = db.execute( + select(SavingsRecord.user_id, SavingsRecord.shop_name) + .where( + SavingsRecord.user_id.in_(user_ids), + SavingsRecord.source == "compare", + SavingsRecord.shop_name.in_(shop_names), + ) + .distinct() + ).all() + ordered_pairs = { + (row.user_id, row.shop_name) + for row in rows + if row.shop_name is not None + } + for item in items: + item.ordered = bool( + item.user_id is not None + and item.store_name + and (item.user_id, item.store_name) in ordered_pairs + ) + + def _attach_comparison_device_details(items: list[ComparisonRecord]) -> None: """给比价记录补充可读机型名,同时保留原始设备编码。""" for item in items: @@ -350,6 +378,7 @@ def list_comparison_records( limit=limit, cursor=cursor, ) _attach_user_info(db, items) + _attach_comparison_order_status(db, items) _attach_comparison_device_details(items) # 「本次比价看广告的预估收益」:按本页 trace_id 一次性聚合(同 _attach_user_info 逐页范式)。 # ad_revenue_yuan 非 ORM 列,仅瞬态挂实例上供 AdminComparisonListItem(from_attributes)读出。 @@ -497,6 +526,7 @@ def get_comparison_record(db: Session, record_id: int) -> ComparisonRecord | Non rec = db.get(ComparisonRecord, record_id) if rec is not None: _attach_user_info(db, [rec]) + _attach_comparison_order_status(db, [rec]) _attach_comparison_device_details([rec]) return rec diff --git a/app/admin/schemas/comparison.py b/app/admin/schemas/comparison.py index 5177ff0..c9f1d43 100644 --- a/app/admin/schemas/comparison.py +++ b/app/admin/schemas/comparison.py @@ -29,6 +29,7 @@ class AdminComparisonListItem(BaseModel): source_price_cents: int | None = None best_price_cents: int | None = None saved_amount_cents: int | None = None + ordered: bool = False # debug 概览 total_ms: int | None = None step_count: int | None = None diff --git a/tests/test_admin_read.py b/tests/test_admin_read.py index a4bd386..084caac 100644 --- a/tests/test_admin_read.py +++ b/tests/test_admin_read.py @@ -640,6 +640,71 @@ def test_comparison_records_show_readable_device_and_rom_version( assert detail.json()["rom_version"] == 4 +def test_comparison_records_show_real_order_status( + admin_client: TestClient, admin_token: str +) -> None: + db = SessionLocal() + try: + user = user_repo.upsert_user_for_login( + db, phone="13800009042", register_channel="sms" + ) + ordered_record = ComparisonRecord( + user_id=user.id, + trace_id="comparison-ordered-shop", + status="success", + store_name="真实下单店", + ) + unordered_record = ComparisonRecord( + user_id=user.id, + trace_id="comparison-demo-order-shop", + status="success", + store_name="演示下单店", + ) + db.add_all([ordered_record, unordered_record]) + db.flush() + db.add_all( + [ + SavingsRecord( + user_id=user.id, + order_amount_cents=1800, + saved_amount_cents=300, + shop_name="真实下单店", + source="compare", + client_event_id="admin-comparison-real-order", + ), + SavingsRecord( + user_id=user.id, + order_amount_cents=1500, + saved_amount_cents=200, + shop_name="演示下单店", + source="demo", + ), + ] + ) + db.commit() + ordered_record_id = ordered_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 + by_trace = {item["trace_id"]: item for item in response.json()["items"]} + assert by_trace["comparison-ordered-shop"]["ordered"] is True + assert by_trace["comparison-demo-order-shop"]["ordered"] is False + + detail = admin_client.get( + f"/admin/api/comparison-records/{ordered_record_id}", + headers=_auth(admin_token), + ) + assert detail.status_code == 200, detail.text + assert detail.json()["ordered"] is True + + def test_ad_coin_audit_full_count_truncate_and_only_mismatch( admin_client: TestClient, admin_token: str ) -> None: