mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 10:08:46 +02:00
Tasa Cero: InstallmentPlan model, cuota service, migration
BAC zero-interest financing: an anchor purchase splits into N monthly cuota transactions (truncate-to-0.10 rule, last cuota absorbs rounding; one cuota per 18th-cut billing cycle on the 19th). Pinned against real Financiamientos data. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -169,6 +169,17 @@ class Transaction(TransactionBase, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
created_at: datetime = Field(default_factory=utcnow)
|
||||
category: Optional[Category] = Relationship(back_populates="transactions")
|
||||
# Tasa Cero: set on generated cuota rows. Plain int (no DB FK) to avoid a
|
||||
# circular transaction<->installmentplan dependency; cascades are handled
|
||||
# explicitly in app.services.installments (SQLite tests don't enforce FKs
|
||||
# anyway). Table-class only so TransactionCreate/n8n payloads can't set it.
|
||||
installment_plan_id: Optional[int] = Field(default=None, index=True)
|
||||
# Tasa Cero: True on the original purchase once converted to a plan; the
|
||||
# anchor stays visible in listings but is excluded from budget/analytics
|
||||
# aggregates (its generated cuotas are counted instead).
|
||||
is_installment_anchor: bool = Field(
|
||||
default=False, sa_column_kwargs={"server_default": "false"}
|
||||
)
|
||||
|
||||
|
||||
class TransactionCreate(TransactionBase):
|
||||
@@ -179,6 +190,8 @@ class TransactionRead(TransactionBase):
|
||||
id: int
|
||||
created_at: datetime
|
||||
category: Optional[CategoryRead] = None
|
||||
installment_plan_id: Optional[int] = None
|
||||
is_installment_anchor: bool = False
|
||||
|
||||
|
||||
class TransactionUpdate(SQLModel):
|
||||
@@ -194,6 +207,39 @@ class TransactionUpdate(SQLModel):
|
||||
deferred_to_next_cycle: Optional[bool] = None
|
||||
|
||||
|
||||
# --- Installment Plan (Tasa Cero) ---
|
||||
|
||||
|
||||
class InstallmentPlanBase(SQLModel):
|
||||
num_installments: int
|
||||
first_installment_date: datetime
|
||||
total_amount: Money = Field(max_digits=15, decimal_places=2)
|
||||
# All cuotas but the last; the last absorbs the rounding remainder.
|
||||
installment_amount: Money = Field(max_digits=15, decimal_places=2)
|
||||
currency: Currency = Currency.CRC
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class InstallmentPlan(InstallmentPlanBase, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
anchor_transaction_id: int = Field(
|
||||
foreign_key="transaction.id", ondelete="CASCADE", unique=True, index=True
|
||||
)
|
||||
created_at: datetime = Field(default_factory=utcnow)
|
||||
|
||||
|
||||
class InstallmentPlanCreate(SQLModel):
|
||||
transaction_id: int
|
||||
num_installments: int = 3
|
||||
first_installment_date: Optional[datetime] = None # default: anchor.date
|
||||
|
||||
|
||||
class InstallmentPlanUpdate(SQLModel):
|
||||
num_installments: Optional[int] = None
|
||||
first_installment_date: Optional[datetime] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
# --- Exchange Rate ---
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user