feat(sources): контракт источников, worker и синк ZenMoney + ЦБ
Source.sync(ctx) -> SyncResult пишет только raw_* и возвращает курсор; локи, журнал, ошибки и продвижение курсора берёт на себя worker/runner. ZenMoney читается единственным доступным способом — POST /v8/diff/ по serverTimestamp; токен живёт сутки, поэтому worker ротирует refresh_token через source_credential. Маппер всегда пересобирает core из полных raw_*, так что удаление в ZenMoney исчезает и у нас. ЦБ ходит мимо прокси (trust_env=False) и отдаёт cp1251 с делением на Nominal. Курсы только по рабочим дням — протяжку по календарю делает аналитика. Планировщик — APScheduler в отдельном процессе, на источник advisory-лок sync:<name>, чтобы ручной запуск не пересёкся с плановым.
This commit is contained in:
@@ -0,0 +1,458 @@
|
||||
"""phase1 zenmoney fx metrics
|
||||
|
||||
Revision ID: 0506c15002c8
|
||||
Revises: 05beabc436ad
|
||||
Create Date: 2026-09-17 22:38:27.588642
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "0506c15002c8"
|
||||
down_revision: str | None = "05beabc436ad"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"category",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("source", sa.String(length=32), nullable=False),
|
||||
sa.Column("source_id", sa.String(length=64), nullable=False),
|
||||
sa.Column("parent_id", sa.Integer(), nullable=True),
|
||||
sa.Column("name", sa.String(length=256), nullable=False),
|
||||
sa.Column("icon", sa.String(length=64), nullable=True),
|
||||
sa.Column("color", sa.BigInteger(), nullable=True),
|
||||
sa.Column("show_income", sa.Boolean(), nullable=False),
|
||||
sa.Column("show_outcome", sa.Boolean(), nullable=False),
|
||||
sa.Column("archived", sa.Boolean(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["parent_id"],
|
||||
["category.id"],
|
||||
name=op.f("fk_category_parent_id_category"),
|
||||
ondelete="SET NULL",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_category")),
|
||||
sa.UniqueConstraint("source", "source_id", name=op.f("uq_category_source_source_id")),
|
||||
)
|
||||
op.create_table(
|
||||
"fx_rate_daily",
|
||||
sa.Column("d", sa.Date(), nullable=False),
|
||||
sa.Column("ccy", sa.String(length=3), nullable=False),
|
||||
sa.Column("rate_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("source", sa.String(length=16), nullable=False),
|
||||
sa.Column("is_carried", sa.Boolean(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("d", "ccy", name=op.f("pk_fx_rate_daily")),
|
||||
)
|
||||
op.create_table(
|
||||
"merchant",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("source", sa.String(length=32), nullable=False),
|
||||
sa.Column("source_id", sa.String(length=64), nullable=False),
|
||||
sa.Column("name", sa.String(length=256), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_merchant")),
|
||||
sa.UniqueConstraint("source", "source_id", name=op.f("uq_merchant_source_source_id")),
|
||||
)
|
||||
op.create_table(
|
||||
"metric_cash_flow_monthly",
|
||||
sa.Column("month", sa.Date(), nullable=False),
|
||||
sa.Column("income_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("expense_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("baseline_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("one_off_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("savings_transfer_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("savings_rate", sa.Numeric(precision=24, scale=10), nullable=True),
|
||||
sa.Column("txn_count", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"computed_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("month", name=op.f("pk_metric_cash_flow_monthly")),
|
||||
)
|
||||
op.create_table(
|
||||
"metric_data_quality",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("check_name", sa.String(length=64), nullable=False),
|
||||
sa.Column("severity", sa.String(length=8), nullable=False),
|
||||
sa.Column("detail", sa.Text(), nullable=False),
|
||||
sa.Column("count", sa.Integer(), nullable=False),
|
||||
sa.Column("ref", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column(
|
||||
"computed_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_metric_data_quality")),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_metric_data_quality_check_name"),
|
||||
"metric_data_quality",
|
||||
["check_name"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_table(
|
||||
"metric_net_worth_daily",
|
||||
sa.Column("d", sa.Date(), nullable=False),
|
||||
sa.Column("total_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("liquid_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("savings_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("investment_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("debt_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("by_currency", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column("missing_fx_count", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"computed_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("d", name=op.f("pk_metric_net_worth_daily")),
|
||||
)
|
||||
op.create_table(
|
||||
"metric_refresh_log",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"started_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("trigger", sa.String(length=32), nullable=False),
|
||||
sa.Column("error", sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_metric_refresh_log")),
|
||||
)
|
||||
op.create_table(
|
||||
"metric_runway",
|
||||
sa.Column("as_of", sa.Date(), nullable=False),
|
||||
sa.Column("liquid_reserve_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("avg_baseline_3m_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("runway_months", sa.Numeric(precision=24, scale=10), nullable=True),
|
||||
sa.Column(
|
||||
"computed_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("as_of", name=op.f("pk_metric_runway")),
|
||||
)
|
||||
op.create_table(
|
||||
"raw_cbr_rate",
|
||||
sa.Column("rate_date", sa.Date(), nullable=False),
|
||||
sa.Column("ccy", sa.String(length=3), nullable=False),
|
||||
sa.Column("nominal", sa.Integer(), nullable=False),
|
||||
sa.Column("value", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column(
|
||||
"fetched_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("rate_date", "ccy", name=op.f("pk_raw_cbr_rate")),
|
||||
)
|
||||
op.create_table(
|
||||
"raw_zenmoney_deletion",
|
||||
sa.Column("entity_type", sa.String(length=32), nullable=False),
|
||||
sa.Column("id", sa.String(length=64), nullable=False),
|
||||
sa.Column("stamp", sa.BigInteger(), nullable=True),
|
||||
sa.Column(
|
||||
"deleted_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("entity_type", "id", name=op.f("pk_raw_zenmoney_deletion")),
|
||||
)
|
||||
op.create_table(
|
||||
"raw_zenmoney_entity",
|
||||
sa.Column("entity_type", sa.String(length=32), nullable=False),
|
||||
sa.Column("id", sa.String(length=64), nullable=False),
|
||||
sa.Column("changed", sa.BigInteger(), nullable=True),
|
||||
sa.Column("payload", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
|
||||
sa.Column(
|
||||
"ingested_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("entity_type", "id", name=op.f("pk_raw_zenmoney_entity")),
|
||||
)
|
||||
op.create_table(
|
||||
"rule",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"kind",
|
||||
sa.Enum(
|
||||
"savings",
|
||||
"one_off",
|
||||
"category",
|
||||
"payee",
|
||||
"broker_target",
|
||||
"ignore",
|
||||
name="rule_kind",
|
||||
),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"match_type",
|
||||
sa.Enum("id", "payee", "comment", "category", "mcc", "account", name="rule_match_type"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("pattern", sa.String(length=512), nullable=False),
|
||||
sa.Column("value", sa.String(length=512), nullable=True),
|
||||
sa.Column("note", sa.Text(), nullable=True),
|
||||
sa.Column("enabled", sa.Boolean(), nullable=False),
|
||||
sa.Column("priority", sa.Integer(), nullable=False),
|
||||
sa.Column("last_matched_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("match_count", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_rule")),
|
||||
)
|
||||
op.create_table(
|
||||
"trip",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.String(length=256), nullable=False),
|
||||
sa.Column("date_from", sa.Date(), nullable=False),
|
||||
sa.Column("date_to", sa.Date(), nullable=False),
|
||||
sa.Column("country", sa.String(length=2), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_trip")),
|
||||
)
|
||||
op.create_table(
|
||||
"cash_txn",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("source", sa.String(length=32), nullable=False),
|
||||
sa.Column("source_id", sa.String(length=64), nullable=False),
|
||||
sa.Column("ts", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("date", sa.Date(), nullable=False),
|
||||
sa.Column("income", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("income_currency", sa.String(length=3), nullable=True),
|
||||
sa.Column("income_account_id", sa.Integer(), nullable=True),
|
||||
sa.Column("outcome", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("outcome_currency", sa.String(length=3), nullable=True),
|
||||
sa.Column("outcome_account_id", sa.Integer(), nullable=True),
|
||||
sa.Column("op_income", sa.Numeric(precision=24, scale=10), nullable=True),
|
||||
sa.Column("op_income_currency", sa.String(length=3), nullable=True),
|
||||
sa.Column("op_outcome", sa.Numeric(precision=24, scale=10), nullable=True),
|
||||
sa.Column("op_outcome_currency", sa.String(length=3), nullable=True),
|
||||
sa.Column("payee", sa.String(length=512), nullable=True),
|
||||
sa.Column("original_payee", sa.String(length=512), nullable=True),
|
||||
sa.Column("merchant_id", sa.Integer(), nullable=True),
|
||||
sa.Column("comment", sa.Text(), nullable=True),
|
||||
sa.Column("mcc", sa.Integer(), nullable=True),
|
||||
sa.Column("hold", sa.Boolean(), nullable=False),
|
||||
sa.Column("deleted", sa.Boolean(), nullable=False),
|
||||
sa.Column("changed", sa.BigInteger(), nullable=True),
|
||||
sa.Column("primary_category_id", sa.Integer(), nullable=True),
|
||||
sa.Column(
|
||||
"flow_type",
|
||||
sa.Enum(
|
||||
"income",
|
||||
"expense",
|
||||
"internal_transfer",
|
||||
"savings_transfer",
|
||||
"broker_external_flow",
|
||||
"deleted",
|
||||
"other",
|
||||
name="flow_type",
|
||||
),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("category_id", sa.Integer(), nullable=True),
|
||||
sa.Column("payee_canonical", sa.String(length=512), nullable=True),
|
||||
sa.Column("is_one_off", sa.Boolean(), nullable=False),
|
||||
sa.Column("trip_id", sa.Integer(), nullable=True),
|
||||
sa.Column("meta", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["category_id"],
|
||||
["category.id"],
|
||||
name=op.f("fk_cash_txn_category_id_category"),
|
||||
ondelete="SET NULL",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["income_account_id"],
|
||||
["account.id"],
|
||||
name=op.f("fk_cash_txn_income_account_id_account"),
|
||||
ondelete="SET NULL",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["merchant_id"],
|
||||
["merchant.id"],
|
||||
name=op.f("fk_cash_txn_merchant_id_merchant"),
|
||||
ondelete="SET NULL",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["outcome_account_id"],
|
||||
["account.id"],
|
||||
name=op.f("fk_cash_txn_outcome_account_id_account"),
|
||||
ondelete="SET NULL",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["primary_category_id"],
|
||||
["category.id"],
|
||||
name=op.f("fk_cash_txn_primary_category_id_category"),
|
||||
ondelete="SET NULL",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["trip_id"], ["trip.id"], name=op.f("fk_cash_txn_trip_id_trip"), ondelete="SET NULL"
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_cash_txn")),
|
||||
sa.UniqueConstraint("source", "source_id", name=op.f("uq_cash_txn_source_source_id")),
|
||||
)
|
||||
op.create_index(op.f("ix_cash_txn_date"), "cash_txn", ["date"], unique=False)
|
||||
op.create_index(
|
||||
op.f("ix_cash_txn_income_account_id"), "cash_txn", ["income_account_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_cash_txn_outcome_account_id"), "cash_txn", ["outcome_account_id"], unique=False
|
||||
)
|
||||
op.create_table(
|
||||
"metric_spending_by_category",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("month", sa.Date(), nullable=False),
|
||||
sa.Column("category_id", sa.Integer(), nullable=True),
|
||||
sa.Column("root_category_id", sa.Integer(), nullable=True),
|
||||
sa.Column("amount_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("txn_count", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"computed_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["category_id"],
|
||||
["category.id"],
|
||||
name=op.f("fk_metric_spending_by_category_category_id_category"),
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["root_category_id"],
|
||||
["category.id"],
|
||||
name=op.f("fk_metric_spending_by_category_root_category_id_category"),
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_metric_spending_by_category")),
|
||||
sa.UniqueConstraint(
|
||||
"month",
|
||||
"category_id",
|
||||
name=op.f("uq_metric_spending_by_category_month_category_id"),
|
||||
postgresql_nulls_not_distinct=True,
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_metric_spending_by_category_month"),
|
||||
"metric_spending_by_category",
|
||||
["month"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_table(
|
||||
"cash_txn_tag",
|
||||
sa.Column("txn_id", sa.Integer(), nullable=False),
|
||||
sa.Column("ord", sa.Integer(), nullable=False),
|
||||
sa.Column("category_id", sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["category_id"],
|
||||
["category.id"],
|
||||
name=op.f("fk_cash_txn_tag_category_id_category"),
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["txn_id"],
|
||||
["cash_txn.id"],
|
||||
name=op.f("fk_cash_txn_tag_txn_id_cash_txn"),
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("txn_id", "ord", name=op.f("pk_cash_txn_tag")),
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_cash_txn_tag_category_id"), "cash_txn_tag", ["category_id"], unique=False
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f("ix_cash_txn_tag_category_id"), table_name="cash_txn_tag")
|
||||
op.drop_table("cash_txn_tag")
|
||||
op.drop_index(
|
||||
op.f("ix_metric_spending_by_category_month"), table_name="metric_spending_by_category"
|
||||
)
|
||||
op.drop_table("metric_spending_by_category")
|
||||
op.drop_index(op.f("ix_cash_txn_outcome_account_id"), table_name="cash_txn")
|
||||
op.drop_index(op.f("ix_cash_txn_income_account_id"), table_name="cash_txn")
|
||||
op.drop_index(op.f("ix_cash_txn_date"), table_name="cash_txn")
|
||||
op.drop_table("cash_txn")
|
||||
op.drop_table("trip")
|
||||
op.drop_table("rule")
|
||||
op.drop_table("raw_zenmoney_entity")
|
||||
op.drop_table("raw_zenmoney_deletion")
|
||||
op.drop_table("raw_cbr_rate")
|
||||
op.drop_table("metric_runway")
|
||||
op.drop_table("metric_refresh_log")
|
||||
op.drop_table("metric_net_worth_daily")
|
||||
op.drop_index(op.f("ix_metric_data_quality_check_name"), table_name="metric_data_quality")
|
||||
op.drop_table("metric_data_quality")
|
||||
op.drop_table("metric_cash_flow_monthly")
|
||||
op.drop_table("merchant")
|
||||
op.drop_table("fx_rate_daily")
|
||||
op.drop_table("category")
|
||||
# ### end Alembic commands ###
|
||||
for enum_name in ("flow_type", "rule_kind", "rule_match_type"):
|
||||
sa.Enum(name=enum_name).drop(op.get_bind(), checkfirst=True)
|
||||
@@ -0,0 +1,42 @@
|
||||
"""account balance columns
|
||||
|
||||
Revision ID: b1d54240abb8
|
||||
Revises: 0506c15002c8
|
||||
Create Date: 2026-09-17 22:40:21.874610
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "b1d54240abb8"
|
||||
down_revision: str | None = "0506c15002c8"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
"account", sa.Column("balance", sa.Numeric(precision=24, scale=10), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
"account", sa.Column("start_balance", sa.Numeric(precision=24, scale=10), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
"account", sa.Column("credit_limit", sa.Numeric(precision=24, scale=10), nullable=True)
|
||||
)
|
||||
op.add_column("account", sa.Column("balance_as_of", sa.DateTime(timezone=True), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("account", "balance_as_of")
|
||||
op.drop_column("account", "credit_limit")
|
||||
op.drop_column("account", "start_balance")
|
||||
op.drop_column("account", "balance")
|
||||
# ### end Alembic commands ###
|
||||
Reference in New Issue
Block a user