Refactor code for improved readability and consistency across multiple files
Gitea Actions CI / detect (push) Successful in 3s
Gitea Actions CI / Compose & nginx validation (push) Failing after 14s
Gitea Actions CI / Frontend (lint / type / test / build) (push) Successful in 45s
Gitea Actions CI / Backend (lint / type / test / build) (push) Successful in 1m31s
Gitea Actions CI / detect (push) Successful in 3s
Gitea Actions CI / Compose & nginx validation (push) Failing after 14s
Gitea Actions CI / Frontend (lint / type / test / build) (push) Successful in 45s
Gitea Actions CI / Backend (lint / type / test / build) (push) Successful in 1m31s
This commit is contained in:
@@ -29,9 +29,7 @@ def _check_constraints(bind, table: str) -> list[dict]:
|
||||
|
||||
def _has_unique_constraint(bind, table: str, name: str) -> bool:
|
||||
inspector = sa.inspect(bind)
|
||||
return any(
|
||||
(item.get("name") or "") == name for item in inspector.get_unique_constraints(table)
|
||||
)
|
||||
return any((item.get("name") or "") == name for item in inspector.get_unique_constraints(table))
|
||||
|
||||
|
||||
def _normalized_sqltext(expression: str) -> str:
|
||||
@@ -96,9 +94,7 @@ def upgrade() -> None:
|
||||
),
|
||||
)
|
||||
if not _has_column(bind, "operations", "previous_category_id"):
|
||||
op.add_column(
|
||||
"operations", sa.Column("previous_category_id", sa.Uuid(), nullable=True)
|
||||
)
|
||||
op.add_column("operations", sa.Column("previous_category_id", sa.Uuid(), nullable=True))
|
||||
_replace_check_constraint(bind, "operations", "type_valid", NEW_OPERATION_TYPES)
|
||||
|
||||
if not _has_unique_constraint(bind, "categories", "uq_categories_system_pair"):
|
||||
@@ -172,12 +168,8 @@ def downgrade() -> None:
|
||||
)
|
||||
|
||||
# The preflight proved these system resources have no dependent history.
|
||||
bind.execute(
|
||||
sa.text("DELETE FROM categories WHERE system_key = 'savings'")
|
||||
)
|
||||
bind.execute(
|
||||
sa.text("DELETE FROM accounts WHERE system_key = 'savings'")
|
||||
)
|
||||
bind.execute(sa.text("DELETE FROM categories WHERE system_key = 'savings'"))
|
||||
bind.execute(sa.text("DELETE FROM accounts WHERE system_key = 'savings'"))
|
||||
if _has_unique_constraint(bind, "categories", "uq_categories_system_pair"):
|
||||
with op.batch_alter_table("categories") as batch_op:
|
||||
batch_op.drop_constraint("uq_categories_system_pair", type_="unique")
|
||||
|
||||
@@ -39,10 +39,7 @@ def category_spend(
|
||||
.scalar()
|
||||
)
|
||||
totals[expense.category_id] += expense.amount_kopecks - int(refunded or 0)
|
||||
categories = {
|
||||
item.id: item
|
||||
for item in db.query(Category).filter_by(space_id=space_id).all()
|
||||
}
|
||||
categories = {item.id: item for item in db.query(Category).filter_by(space_id=space_id).all()}
|
||||
return [
|
||||
{
|
||||
"category_id": category_id,
|
||||
|
||||
@@ -28,9 +28,7 @@ class Account(Base, PkMixin, TimestampMixin):
|
||||
|
||||
__tablename__ = "accounts"
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"kind IN ('cash', 'debit', 'credit', 'savings')", name="kind_valid"
|
||||
),
|
||||
CheckConstraint("kind IN ('cash', 'debit', 'credit', 'savings')", name="kind_valid"),
|
||||
CheckConstraint("status IN ('active', 'archived')", name="status_valid"),
|
||||
CheckConstraint("currency = 'RUB'", name="currency_rub_only"),
|
||||
UniqueConstraint("space_id", "id", name="uq_accounts_space_id"),
|
||||
|
||||
@@ -346,9 +346,7 @@ def savings_transfer(operation_id: uuid.UUID, db: Db, context: Context, _csrf: C
|
||||
|
||||
|
||||
@router.post("/operations/{operation_id}/savings-transfer/revert")
|
||||
def savings_transfer_revert(
|
||||
operation_id: uuid.UUID, db: Db, context: Context, _csrf: Csrf
|
||||
) -> dict:
|
||||
def savings_transfer_revert(operation_id: uuid.UUID, db: Db, context: Context, _csrf: Csrf) -> dict:
|
||||
from app.ledger import savings
|
||||
|
||||
operation = savings.revert_savings_conversion(
|
||||
|
||||
@@ -22,17 +22,13 @@ SAVINGS_ACCOUNT_NAME = "Накопления"
|
||||
|
||||
def get_savings_account(db: Session, *, space_id: uuid.UUID) -> Account | None:
|
||||
return (
|
||||
db.query(Account)
|
||||
.filter_by(space_id=space_id, system_key=SAVINGS_SYSTEM_KEY)
|
||||
.one_or_none()
|
||||
db.query(Account).filter_by(space_id=space_id, system_key=SAVINGS_SYSTEM_KEY).one_or_none()
|
||||
)
|
||||
|
||||
|
||||
def _savings_category(db: Session, *, space_id: uuid.UUID) -> Category | None:
|
||||
return (
|
||||
db.query(Category)
|
||||
.filter_by(space_id=space_id, system_key=SAVINGS_SYSTEM_KEY)
|
||||
.one_or_none()
|
||||
db.query(Category).filter_by(space_id=space_id, system_key=SAVINGS_SYSTEM_KEY).one_or_none()
|
||||
)
|
||||
|
||||
|
||||
@@ -143,9 +139,7 @@ def adjust_balance(
|
||||
"Credit account balances cannot be adjusted with a non-negative target",
|
||||
)
|
||||
if target_balance_kopecks < 0:
|
||||
raise bad_request(
|
||||
"LEDGER_ADJUST_TARGET_NEGATIVE", "Target balance cannot be negative"
|
||||
)
|
||||
raise bad_request("LEDGER_ADJUST_TARGET_NEGATIVE", "Target balance cannot be negative")
|
||||
from app.ledger.service import account_balance, ensure_period_open_for_date
|
||||
|
||||
ensure_period_open_for_date(db, space_id=space_id, day=occurred_on)
|
||||
@@ -277,9 +271,7 @@ def convert_expense_to_savings(
|
||||
"operation_id": str(operation.id),
|
||||
"savings_account_id": str(savings_account.id),
|
||||
"amount_kopecks": operation.amount_kopecks,
|
||||
"previous_category_id": (
|
||||
str(previous_category_id) if previous_category_id else None
|
||||
),
|
||||
"previous_category_id": (str(previous_category_id) if previous_category_id else None),
|
||||
},
|
||||
)
|
||||
return operation
|
||||
|
||||
@@ -546,8 +546,19 @@ class TestAlfaSavingsRule:
|
||||
def savings_statement_rows(self) -> list[list[object]]:
|
||||
rows = [list(row) for row in synthetic_alfa_rows()]
|
||||
rows[9] = [
|
||||
None, None, None, None, None, None, None, None, None, None,
|
||||
"Расходы", None, "416,90 RUR",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"Расходы",
|
||||
None,
|
||||
"416,90 RUR",
|
||||
]
|
||||
savings_row = [
|
||||
"03.09.2026",
|
||||
|
||||
@@ -163,9 +163,7 @@ def test_revert_removes_created_savings_transfer(db: Session) -> None:
|
||||
assert db.query(Operation).filter_by(space_id=space.id).count() == 0
|
||||
assert db.query(SourceRecordLink).filter_by(space_id=space.id).count() == 0
|
||||
assert document.status == "ready"
|
||||
source = (
|
||||
db.query(SourceRecord).filter_by(parse_run_id=document.active_parse_run_id).one()
|
||||
)
|
||||
source = db.query(SourceRecord).filter_by(parse_run_id=document.active_parse_run_id).one()
|
||||
# Repeated parse gives the same decision: the hint survives revert.
|
||||
assert source.review_decision == "savings_transfer"
|
||||
|
||||
@@ -350,9 +348,9 @@ def test_unresolved_hold_counts_and_month_filter(db: Session) -> None:
|
||||
|
||||
unresolved = service.unresolved_holds(db, space_id=space.id, document=document)
|
||||
assert len(unresolved) == 2
|
||||
assert service.unresolved_hold_counts(
|
||||
db, space_id=space.id, document_ids=[document.id]
|
||||
) == {document.id: 2}
|
||||
assert service.unresolved_hold_counts(db, space_id=space.id, document_ids=[document.id]) == {
|
||||
document.id: 2
|
||||
}
|
||||
may = service.parse_month_filter("2026-05")
|
||||
assert may == (2026, 5)
|
||||
may_holds = [h for h in unresolved if h.occurred_on and h.occurred_on.month == 5]
|
||||
|
||||
@@ -58,9 +58,7 @@ def category_ids(client: TestClient) -> dict[str, str]:
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
return {
|
||||
item["systemKey"]: item["id"]
|
||||
for item in response.json()
|
||||
if item["systemKey"] is not None
|
||||
item["systemKey"]: item["id"] for item in response.json() if item["systemKey"] is not None
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -97,9 +97,7 @@ def test_adjust_balance_creates_single_signed_posting(db: Session) -> None:
|
||||
assert len(postings) == 1
|
||||
assert postings[0].amount_kopecks == -25_000
|
||||
assert account_balance(db, account) == 475_000
|
||||
event = (
|
||||
db.query(AuditEvent).filter_by(event_type="account.balance_adjusted").one()
|
||||
)
|
||||
event = db.query(AuditEvent).filter_by(event_type="account.balance_adjusted").one()
|
||||
assert event.detail["old_balance_kopecks"] == 500_000
|
||||
assert event.detail["new_balance_kopecks"] == 475_000
|
||||
assert event.detail["delta_kopecks"] == -25_000
|
||||
@@ -235,9 +233,7 @@ def test_convert_expense_to_savings_and_revert_roundtrip(db: Session) -> None:
|
||||
assert account_balance(db, account) == 500_000 - 12_345
|
||||
assert account_balance(db, savings_account) == 12_345
|
||||
|
||||
reverted = revert_savings_conversion(
|
||||
db, space_id=space.id, user=user, operation_id=expense.id
|
||||
)
|
||||
reverted = revert_savings_conversion(db, space_id=space.id, user=user, operation_id=expense.id)
|
||||
assert reverted.type == "expense"
|
||||
assert reverted.counter_account_id is None
|
||||
assert reverted.category_id == category.id
|
||||
@@ -263,9 +259,7 @@ def test_convert_rejects_income_refund_transfer_and_double_conversion(
|
||||
occurred_on=date(2026, 3, 2),
|
||||
)
|
||||
with pytest.raises(AppError) as exc:
|
||||
convert_expense_to_savings(
|
||||
db, space_id=space.id, user=user, operation_id=income.id
|
||||
)
|
||||
convert_expense_to_savings(db, space_id=space.id, user=user, operation_id=income.id)
|
||||
assert exc.value.code == "LEDGER_SAVINGS_CONVERT_INVALID"
|
||||
|
||||
refundable_expense = create_expense(
|
||||
@@ -285,9 +279,7 @@ def test_convert_rejects_income_refund_transfer_and_double_conversion(
|
||||
occurred_on=date(2026, 3, 4),
|
||||
)
|
||||
with pytest.raises(AppError) as exc:
|
||||
convert_expense_to_savings(
|
||||
db, space_id=space.id, user=user, operation_id=refund.id
|
||||
)
|
||||
convert_expense_to_savings(db, space_id=space.id, user=user, operation_id=refund.id)
|
||||
assert exc.value.code == "LEDGER_SAVINGS_CONVERT_INVALID"
|
||||
|
||||
expense = create_expense(
|
||||
@@ -300,9 +292,7 @@ def test_convert_rejects_income_refund_transfer_and_double_conversion(
|
||||
)
|
||||
convert_expense_to_savings(db, space_id=space.id, user=user, operation_id=expense.id)
|
||||
with pytest.raises(AppError) as exc:
|
||||
convert_expense_to_savings(
|
||||
db, space_id=space.id, user=user, operation_id=expense.id
|
||||
)
|
||||
convert_expense_to_savings(db, space_id=space.id, user=user, operation_id=expense.id)
|
||||
assert exc.value.code == "LEDGER_SAVINGS_CONVERT_INVALID"
|
||||
|
||||
transfer = create_transfer(
|
||||
@@ -315,9 +305,7 @@ def test_convert_rejects_income_refund_transfer_and_double_conversion(
|
||||
occurred_on=date(2026, 3, 6),
|
||||
)
|
||||
with pytest.raises(AppError) as exc:
|
||||
convert_expense_to_savings(
|
||||
db, space_id=space.id, user=user, operation_id=transfer.id
|
||||
)
|
||||
convert_expense_to_savings(db, space_id=space.id, user=user, operation_id=transfer.id)
|
||||
assert exc.value.code == "LEDGER_SAVINGS_CONVERT_INVALID"
|
||||
|
||||
|
||||
@@ -341,9 +329,7 @@ def test_convert_rejects_expense_with_confirmed_refund(db: Session) -> None:
|
||||
)
|
||||
|
||||
with pytest.raises(AppError) as exc:
|
||||
convert_expense_to_savings(
|
||||
db, space_id=space.id, user=user, operation_id=expense.id
|
||||
)
|
||||
convert_expense_to_savings(db, space_id=space.id, user=user, operation_id=expense.id)
|
||||
assert exc.value.code == "LEDGER_SAVINGS_CONVERT_HAS_REFUNDS"
|
||||
|
||||
|
||||
@@ -365,14 +351,10 @@ def test_convert_rejects_closed_period_and_cross_space_operation(db: Session) ->
|
||||
period = get_or_create_period(db, space_id=space.id, year=2026, month=3)
|
||||
close_period(db, space_id=space.id, user=user, period=period)
|
||||
with pytest.raises(AppError) as exc:
|
||||
convert_expense_to_savings(
|
||||
db, space_id=space.id, user=user, operation_id=expense.id
|
||||
)
|
||||
convert_expense_to_savings(db, space_id=space.id, user=user, operation_id=expense.id)
|
||||
assert exc.value.code == "BUDGET_PERIOD_CLOSED"
|
||||
with pytest.raises(AppError) as exc:
|
||||
convert_expense_to_savings(
|
||||
db, space_id=other_space.id, user=user, operation_id=expense.id
|
||||
)
|
||||
convert_expense_to_savings(db, space_id=other_space.id, user=user, operation_id=expense.id)
|
||||
assert exc.value.code == "LEDGER_OPERATION_NOT_FOUND"
|
||||
|
||||
|
||||
@@ -490,6 +472,4 @@ def test_operations_on_other_spaces_are_isolated(db: Session) -> None:
|
||||
occurred_on=date(2026, 3, 14),
|
||||
)
|
||||
convert_expense_to_savings(db, space_id=space.id, user=user, operation_id=expense.id)
|
||||
assert (
|
||||
db.query(Operation).filter_by(space_id=other_space.id).count() == 0
|
||||
)
|
||||
assert db.query(Operation).filter_by(space_id=other_space.id).count() == 0
|
||||
|
||||
Reference in New Issue
Block a user