1313from sqlalchemy .orm import joinedload
1414from sqlalchemy .sql .expression import Select
1515from sqlmodel import Session , col , select
16+ from sqlmodel .sql .expression import SelectOfScalar
1617
1718from actual .crypto import is_uuid
1819from actual .database import (
1920 Accounts ,
21+ BaseBudgets ,
2022 Categories ,
2123 CategoryGroups ,
2224 CategoryMapping ,
@@ -56,8 +58,8 @@ def _transactions_base_query(
5658 account : Accounts | str | None | None = None ,
5759 category : Categories | str | None = None ,
5860 include_deleted : bool = False ,
59- ) -> Select :
60- query = (
61+ ) -> SelectOfScalar [ Transactions ] :
62+ query : SelectOfScalar [ Transactions ] = (
6163 select (Transactions )
6264 .options (
6365 joinedload (Transactions .account ),
@@ -232,7 +234,7 @@ def match_transaction(
232234 query = _transactions_base_query (
233235 s , date - datetime .timedelta (days = 7 ), date + datetime .timedelta (days = 8 ), account = account
234236 ).where (col (Transactions .amount ) == round (amount * 100 ))
235- results : list [Transactions ] = s .exec (query ).all () # noqa
237+ results : list [Transactions ] = list ( s .exec (query ).all ())
236238 # filter out the ones that were already matched
237239 if already_matched :
238240 matched = {t .id for t in already_matched }
@@ -535,13 +537,17 @@ def create_split(s: Session, transaction: Transactions, amount: float | decimal.
535537 return split
536538
537539
538- def _base_query (instance : type [T ], name : str | None = None , include_deleted : bool = False ) -> Select :
540+ def _base_query (instance : type [T ], name : str | None = None , include_deleted : bool = False ) -> SelectOfScalar [ T ] :
539541 """Internal method to reduce querying complexity on sub-functions."""
540542 query = select (instance )
541543 if not include_deleted :
542- query = query .where (sqlalchemy .func .coalesce (instance .tombstone , 0 ) == 0 )
544+ tombstone_col = getattr (instance , "tombstone" , None )
545+ if tombstone_col is not None :
546+ query = query .where (sqlalchemy .func .coalesce (tombstone_col , 0 ) == 0 )
543547 if name :
544- query = query .where (instance .name .ilike (f"%{ sqlalchemy .text (name ).compile ()} %" ))
548+ name_col = getattr (instance , "name" , None )
549+ if name_col is not None :
550+ query = query .where (name_col .ilike (f"%{ sqlalchemy .text (name ).compile ()} %" ))
545551 return query
546552
547553
@@ -839,7 +845,7 @@ def get_or_create_account(s: Session, name: str | Accounts) -> Accounts:
839845 return account
840846
841847
842- def _get_budget_table (s : Session ) -> type [ReflectBudgets | ZeroBudgets ]:
848+ def _get_budget_table (s : Session ) -> type [ZeroBudgets ] | type [ ReflectBudgets ]:
843849 """
844850 Finds out which type of budget the user uses. The types are:
845851
@@ -858,7 +864,7 @@ def _get_budget_table(s: Session) -> type[ReflectBudgets | ZeroBudgets]:
858864
859865def get_budgets (
860866 s : Session , month : datetime .date | None = None , category : str | Categories | None = None
861- ) -> typing .Sequence [ZeroBudgets | ReflectBudgets ]:
867+ ) -> typing .Sequence [BaseBudgets ]:
862868 """
863869 Returns a list of all available budgets.
864870
@@ -889,7 +895,7 @@ def get_budgets(
889895 return s .exec (query ).unique ().all ()
890896
891897
892- def get_budget (s : Session , month : datetime .date , category : str | Categories ) -> ZeroBudgets | ReflectBudgets | None :
898+ def get_budget (s : Session , month : datetime .date , category : str | Categories ) -> BaseBudgets | None :
893899 """
894900 Gets an existing budget by category name, returns `None` if not found.
895901
@@ -910,7 +916,7 @@ def create_budget(
910916 category : str | Categories ,
911917 amount : decimal .Decimal | float | int = 0.0 ,
912918 carryover : bool | None = None ,
913- ) -> ZeroBudgets | ReflectBudgets :
919+ ) -> BaseBudgets :
914920 """
915921 Gets an existing budget based on the month and category. If it already exists, the amount will be replaced by
916922 the new amount.
@@ -1140,6 +1146,32 @@ def get_schedules(
11401146 return s .exec (query ).all ()
11411147
11421148
1149+ @typing .overload
1150+ def create_schedule (
1151+ s : Session ,
1152+ date : datetime .date | datetime .datetime | Schedule ,
1153+ amount : tuple [decimal .Decimal , decimal .Decimal ] | tuple [float , float ],
1154+ amount_operation : typing .Literal ["isbetween" ],
1155+ name : str | None ,
1156+ payee : str | Payees | None ,
1157+ account : str | Accounts | None ,
1158+ posts_transaction : bool ,
1159+ ) -> Schedules : ...
1160+
1161+
1162+ @typing .overload
1163+ def create_schedule (
1164+ s : Session ,
1165+ date : datetime .date | datetime .datetime | Schedule ,
1166+ amount : decimal .Decimal | float ,
1167+ amount_operation : typing .Literal ["is" , "isapprox" ],
1168+ name : str | None ,
1169+ payee : str | Payees | None ,
1170+ account : str | Accounts | None ,
1171+ posts_transaction : bool ,
1172+ ) -> Schedules : ...
1173+
1174+
11431175def create_schedule (
11441176 s : Session ,
11451177 date : datetime .date | datetime .datetime | Schedule ,
@@ -1171,9 +1203,6 @@ def create_schedule(
11711203 :param posts_transaction: Whether the schedule should auto-post transactions on your behalf. Defaults to false.
11721204 :return: Rule database object created.
11731205 """
1174- if amount_operation == "isbetween" and not isinstance (amount , tuple ):
1175- raise ActualError ("When using 'isbetween', amount must be a tuple (num1, num2), where num1 < num2." )
1176-
11771206 schedule_id = str (uuid .uuid4 ())
11781207 conditions = []
11791208 # Handle the payee condition
@@ -1197,6 +1226,8 @@ def create_schedule(
11971226 )
11981227 # Handle the amount condition
11991228 if amount_operation == "isbetween" :
1229+ if not isinstance (amount , tuple ):
1230+ raise ActualError ("When using 'isbetween', amount must be a tuple (num1, num2), where num1 < num2." )
12001231 conditions .append (
12011232 Condition (
12021233 field = "amount" ,
@@ -1205,6 +1236,8 @@ def create_schedule(
12051236 )
12061237 )
12071238 else :
1239+ if isinstance (amount , tuple ):
1240+ raise ActualError (f"When using '{ amount_operation } ', amount must be a single decimal number." )
12081241 conditions .append (Condition (field = "amount" , op = ConditionType (amount_operation ), value = decimal_to_cents (amount )))
12091242
12101243 actions = [
0 commit comments