"""coupon state tables (领券今日状态:弹窗频控 engagement + 领券记录 claim) Revision ID: coupon_state_tables Revises: 11a1d08c6f55 Create Date: 2026-06-08 00:00:00.000000 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. revision: str = 'coupon_state_tables' down_revision: Union[str, Sequence[str], None] = '11a1d08c6f55' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None # PG 上为 JSONB,其它(SQLite dev)为 JSON——与模型层 with_variant 对齐 _JSON = sa.JSON().with_variant(postgresql.JSONB(), 'postgresql') def upgrade() -> None: # 领券记录(资产层,当前不参与去重判断) op.create_table( 'coupon_claim_record', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('device_id', sa.String(length=64), nullable=False), sa.Column('user_id', sa.Integer(), nullable=True), sa.Column('coupon_id', sa.String(length=64), nullable=False), sa.Column('claim_date', sa.Date(), nullable=False), sa.Column('status', sa.String(length=24), nullable=False), sa.Column('vendor', sa.String(length=48), nullable=True), sa.Column('coupon_name', sa.String(length=128), nullable=True), sa.Column('claimed_count', sa.Integer(), nullable=True), sa.Column('trace_id', sa.String(length=64), nullable=True), sa.Column('reason', sa.String(length=255), nullable=True), sa.Column('extra', _JSON, nullable=True), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('device_id', 'coupon_id', 'claim_date', name='uq_coupon_claim_device_coupon_date'), ) with op.batch_alter_table('coupon_claim_record', schema=None) as batch_op: batch_op.create_index('ix_coupon_claim_device_date', ['device_id', 'claim_date'], unique=False) batch_op.create_index(batch_op.f('ix_coupon_claim_record_user_id'), ['user_id'], unique=False) batch_op.create_index(batch_op.f('ix_coupon_claim_record_trace_id'), ['trace_id'], unique=False) # 弹窗频控(今天 engage 过就不再弹) op.create_table( 'coupon_prompt_engagement', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('device_id', sa.String(length=64), nullable=False), sa.Column('user_id', sa.Integer(), nullable=True), sa.Column('engage_date', sa.Date(), nullable=False), sa.Column('engage_type', sa.String(length=16), nullable=False), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('device_id', 'engage_date', name='uq_coupon_engage_device_date'), ) with op.batch_alter_table('coupon_prompt_engagement', schema=None) as batch_op: batch_op.create_index(batch_op.f('ix_coupon_prompt_engagement_user_id'), ['user_id'], unique=False) def downgrade() -> None: with op.batch_alter_table('coupon_prompt_engagement', schema=None) as batch_op: batch_op.drop_index(batch_op.f('ix_coupon_prompt_engagement_user_id')) op.drop_table('coupon_prompt_engagement') with op.batch_alter_table('coupon_claim_record', schema=None) as batch_op: batch_op.drop_index(batch_op.f('ix_coupon_claim_record_trace_id')) batch_op.drop_index(batch_op.f('ix_coupon_claim_record_user_id')) batch_op.drop_index('ix_coupon_claim_device_date') op.drop_table('coupon_claim_record')