Files
shaguabijia-app-server/app/models/savings.py
T
zhangxianze 288766443a feat(记账): 比价省钱记账后端 M1(savings_record 唯一真相表 + /order/report + demo 兜底) (#10)
从最新 origin/main 重拉的干净分支,只含本人记账改动,不含 marco 的归因订单(order_record)——后者是另一条未合并分支 feat/user-order-records 的功能,此前被误用 git add -A 和记账混进了 feat/savings-accounting-m1。本分支不带它:savings_record 升级为唯一真相表(新增原价/比价价/支付渠道/源平台/幂等键等列,迁移 savings_report_fields 直接挂 f01db5d77dac、不经 order_record_table);/order/report 改写 savings_record(source='compare'),省额=源平台原价−实付;展示 /api/v1/savings/* 真实优先 demo 兜底。无 order_record 的 model/repo/迁移/路由。alembic 单一 head + pytest 54 passed。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: xianze <ze@192.168.0.138>
Reviewed-on: #10
Co-authored-by: zhangxianze <zhangxianze@wonderable.ai>
Co-committed-by: zhangxianze <zhangxianze@wonderable.ai>
2026-06-01 21:29:52 +08:00

67 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""省钱记录表。
是 profile「累计帮你省了」和「省钱战绩」的唯一数据源:每完成一次比价/下单,
省了多少记一行。本期比价还没接后端,数据由 demo seeder 幂等灌入(source='demo'),
聚合接口照常做真实 SUM/分组;等比价真接入后,改成上报写入、停掉 seeder 即可。
"""
from __future__ import annotations
from datetime import datetime
from sqlalchemy import JSON, DateTime, ForeignKey, Integer, String, UniqueConstraint, func
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class SavingsRecord(Base):
__tablename__ = "savings_record"
__table_args__ = (
# 真实上报(source='compare')按 (user, client_event_id) 幂等;
# demo 行 client_event_id 为 NULL,不参与唯一性冲突(SQLite/PG 均允许多 NULL)
UniqueConstraint("user_id", "client_event_id", name="uq_savings_user_event"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
user_id: Mapped[int] = mapped_column(
Integer, ForeignKey("user.id"), index=True, nullable=False
)
# 订单金额 / 省下金额,单位:分
order_amount_cents: Mapped[int] = mapped_column(Integer, nullable=False)
saved_amount_cents: Mapped[int] = mapped_column(Integer, nullable=False)
platform: Mapped[str | None] = mapped_column(String(32), nullable=True)
title: Mapped[str | None] = mapped_column(String(128), nullable=True)
# 店铺名(订单明细卡标题,如「窑鸡王(王府井店)」)
shop_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
# 菜品名列表:PG 用 JSONB(可建 GIN 索引),SQLite 用 JSON(TEXT)。前 2 道直接展示,其余收进「还有 N 道菜」。
dishes: Mapped[list[str]] = mapped_column(
JSON().with_variant(JSONB(), "postgresql"), nullable=False, default=list
)
# 来源:demo(演示) / compare(真实比价上报)
source: Mapped[str] = mapped_column(String(16), nullable=False, default="compare")
# ===== 真实比价上报(source='compare')新增字段;demo 行这些均为 NULL =====
# 源平台原价(用户原本要付的钱,分);省额 saved_amount_cents = original order_amount(实付)
original_price_cents: Mapped[int | None] = mapped_column(Integer, nullable=True)
# 我们当时给出的比价价(分),审计/备用展示
compared_price_cents: Mapped[int | None] = mapped_column(Integer, nullable=True)
# 支付渠道:wechat / alipay
pay_channel: Mapped[str | None] = mapped_column(String(16), nullable=True)
# 实际下单平台包名
platform_package: Mapped[str | None] = mapped_column(String(128), nullable=True)
# 源平台展示名(如「美团」),用于「原价 ¥36.8(美团)」展示
source_platform_name: Mapped[str | None] = mapped_column(String(32), nullable=True)
# 源平台重进链接(预留:后续「重新进店/重复下单」用,本期只存不展示)
source_deeplink: Mapped[str | None] = mapped_column(String(512), nullable=True)
# 客户端幂等键(UUID);demo 行为 NULL
client_event_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
device_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), index=True, nullable=False
)
def __repr__(self) -> str: # pragma: no cover
return f"<SavingsRecord id={self.id} user_id={self.user_id} saved={self.saved_amount_cents}>"