mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-13 02:56:39 +07:00
feat: auto-capitalize the first letter of transaction descriptions
Transactions/recurring templates entered with mixed casing ("траты на
продукты") are now capitalized on create/update via a Pydantic validator,
so the fix applies to every client, not just the web UI. Adds a one-off
backfill script for existing rows.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
12
backend/app/core/text.py
Normal file
12
backend/app/core/text.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Shared text-formatting helpers."""
|
||||
|
||||
|
||||
def capitalize_first_letter(value: str) -> str:
|
||||
"""Uppercase the first character, leaving the rest of the string untouched.
|
||||
|
||||
Unlike str.capitalize()/str.title(), this never lowercases the rest of the
|
||||
string — "iPhone 15" stays "iPhone 15", not "Iphone 15".
|
||||
"""
|
||||
if not value:
|
||||
return value
|
||||
return value[0].upper() + value[1:]
|
||||
@@ -1,8 +1,9 @@
|
||||
from datetime import date as date_
|
||||
from decimal import Decimal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from app.core.text import capitalize_first_letter
|
||||
from app.models.enums import RecurringFrequency, TransactionType
|
||||
|
||||
|
||||
@@ -18,6 +19,13 @@ class RecurringTransactionCreate(BaseModel):
|
||||
frequency: RecurringFrequency
|
||||
anchor_date: date_
|
||||
|
||||
# Keeps templates consistent with regular transactions — see
|
||||
# app/schemas/transaction.py for the same rule.
|
||||
@field_validator("description")
|
||||
@classmethod
|
||||
def _capitalize_description(cls, value: str) -> str:
|
||||
return capitalize_first_letter(value)
|
||||
|
||||
|
||||
class RecurringTransactionUpdate(BaseModel):
|
||||
account_id: int | None = None
|
||||
@@ -32,6 +40,11 @@ class RecurringTransactionUpdate(BaseModel):
|
||||
anchor_date: date_ | None = None
|
||||
is_active: bool | None = None
|
||||
|
||||
@field_validator("description")
|
||||
@classmethod
|
||||
def _capitalize_description(cls, value: str | None) -> str | None:
|
||||
return capitalize_first_letter(value) if value is not None else None
|
||||
|
||||
|
||||
class RecurringTransactionRead(BaseModel):
|
||||
id: int
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from datetime import date as date_
|
||||
from decimal import Decimal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
|
||||
from app.core.text import capitalize_first_letter
|
||||
from app.models.enums import TransactionType
|
||||
from app.schemas.account import AccountRead
|
||||
from app.schemas.category import CategoryRead
|
||||
@@ -19,6 +20,13 @@ class TransactionBase(BaseModel):
|
||||
notes: str | None = None
|
||||
date: date_
|
||||
|
||||
# Auto-capitalizes "траты на продукты" -> "Траты на продукты" so mixed
|
||||
# casing from quick manual entry doesn't need fixing by hand later.
|
||||
@field_validator("description")
|
||||
@classmethod
|
||||
def _capitalize_description(cls, value: str) -> str:
|
||||
return capitalize_first_letter(value)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_type_specific_fields(self) -> "TransactionBase":
|
||||
if self.type == TransactionType.TRANSFER and not self.transfer_account_id:
|
||||
@@ -47,6 +55,11 @@ class TransactionUpdate(BaseModel):
|
||||
notes: str | None = None
|
||||
date: date_ | None = None
|
||||
|
||||
@field_validator("description")
|
||||
@classmethod
|
||||
def _capitalize_description(cls, value: str | None) -> str | None:
|
||||
return capitalize_first_letter(value) if value is not None else None
|
||||
|
||||
|
||||
class TransactionRead(TransactionBase):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
Reference in New Issue
Block a user