Merge to LOAF-PROD for Demo #27

Merged
andika merged 10 commits from dev into loaf-prod 2026-02-02 11:11:36 +00:00
Showing only changes of commit 6f8ec1d254 - Show all commits

View File

@@ -19,49 +19,63 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None: def upgrade() -> None:
conn = op.get_bind()
# Create PaymentMethodType enum # Create PaymentMethodType enum
paymentmethodtype = postgresql.ENUM( paymentmethodtype = postgresql.ENUM(
'card', 'cash', 'bank_transfer', 'check', 'card', 'cash', 'bank_transfer', 'check',
name='paymentmethodtype', name='paymentmethodtype',
create_type=False create_type=False
) )
paymentmethodtype.create(op.get_bind(), checkfirst=True) paymentmethodtype.create(conn, checkfirst=True)
# Add stripe_customer_id to users table # Check if stripe_customer_id column exists on users table
op.add_column('users', sa.Column( result = conn.execute(sa.text("""
'stripe_customer_id', SELECT column_name FROM information_schema.columns
sa.String(), WHERE table_name = 'users' AND column_name = 'stripe_customer_id'
nullable=True, """))
comment='Stripe Customer ID for payment method management' if result.fetchone() is None:
)) # Add stripe_customer_id to users table
op.create_index('ix_users_stripe_customer_id', 'users', ['stripe_customer_id']) op.add_column('users', sa.Column(
'stripe_customer_id',
sa.String(),
nullable=True,
comment='Stripe Customer ID for payment method management'
))
op.create_index('ix_users_stripe_customer_id', 'users', ['stripe_customer_id'])
# Create payment_methods table # Check if payment_methods table exists
op.create_table( result = conn.execute(sa.text("""
'payment_methods', SELECT table_name FROM information_schema.tables
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True), WHERE table_name = 'payment_methods'
sa.Column('user_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False), """))
sa.Column('stripe_payment_method_id', sa.String(), nullable=True, unique=True, comment='Stripe pm_xxx reference'), if result.fetchone() is None:
sa.Column('card_brand', sa.String(20), nullable=True, comment='Card brand: visa, mastercard, amex, etc.'), # Create payment_methods table
sa.Column('card_last4', sa.String(4), nullable=True, comment='Last 4 digits of card'), op.create_table(
sa.Column('card_exp_month', sa.Integer(), nullable=True, comment='Card expiration month'), 'payment_methods',
sa.Column('card_exp_year', sa.Integer(), nullable=True, comment='Card expiration year'), sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('card_funding', sa.String(20), nullable=True, comment='Card funding type: credit, debit, prepaid'), sa.Column('user_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False),
sa.Column('payment_type', paymentmethodtype, nullable=False, server_default='card'), sa.Column('stripe_payment_method_id', sa.String(), nullable=True, unique=True, comment='Stripe pm_xxx reference'),
sa.Column('is_default', sa.Boolean(), nullable=False, server_default='false', comment='Whether this is the default payment method for auto-renewals'), sa.Column('card_brand', sa.String(20), nullable=True, comment='Card brand: visa, mastercard, amex, etc.'),
sa.Column('is_active', sa.Boolean(), nullable=False, server_default='true', comment='Soft delete flag - False means removed'), sa.Column('card_last4', sa.String(4), nullable=True, comment='Last 4 digits of card'),
sa.Column('is_manual', sa.Boolean(), nullable=False, server_default='false', comment='True for manually recorded methods (cash/check)'), sa.Column('card_exp_month', sa.Integer(), nullable=True, comment='Card expiration month'),
sa.Column('manual_notes', sa.Text(), nullable=True, comment='Admin notes for manual payment methods'), sa.Column('card_exp_year', sa.Integer(), nullable=True, comment='Card expiration year'),
sa.Column('created_by', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, comment='Admin who added this on behalf of user'), sa.Column('card_funding', sa.String(20), nullable=True, comment='Card funding type: credit, debit, prepaid'),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.Column('payment_type', paymentmethodtype, nullable=False, server_default='card'),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now()), sa.Column('is_default', sa.Boolean(), nullable=False, server_default='false', comment='Whether this is the default payment method for auto-renewals'),
) sa.Column('is_active', sa.Boolean(), nullable=False, server_default='true', comment='Soft delete flag - False means removed'),
sa.Column('is_manual', sa.Boolean(), nullable=False, server_default='false', comment='True for manually recorded methods (cash/check)'),
sa.Column('manual_notes', sa.Text(), nullable=True, comment='Admin notes for manual payment methods'),
sa.Column('created_by', postgresql.UUID(as_uuid=True), sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, comment='Admin who added this on behalf of user'),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now()),
)
# Create indexes # Create indexes
op.create_index('ix_payment_methods_user_id', 'payment_methods', ['user_id']) op.create_index('ix_payment_methods_user_id', 'payment_methods', ['user_id'])
op.create_index('ix_payment_methods_stripe_pm_id', 'payment_methods', ['stripe_payment_method_id']) op.create_index('ix_payment_methods_stripe_pm_id', 'payment_methods', ['stripe_payment_method_id'])
op.create_index('idx_payment_method_user_default', 'payment_methods', ['user_id', 'is_default']) op.create_index('idx_payment_method_user_default', 'payment_methods', ['user_id', 'is_default'])
op.create_index('idx_payment_method_active', 'payment_methods', ['user_id', 'is_active']) op.create_index('idx_payment_method_active', 'payment_methods', ['user_id', 'is_active'])
def downgrade() -> None: def downgrade() -> None: