Implemented fixtures & Implementing Resource pattern
This commit is contained in:
@@ -1,44 +1,68 @@
|
|||||||
|
from datetime import date
|
||||||
|
|
||||||
from account.models import Account
|
from account.models import Account
|
||||||
from account.schemas import AccountCreate
|
from account.schemas import AccountCreate, CategoryCreate
|
||||||
|
|
||||||
def inject_fixtures(session):
|
def inject_fixtures(session):
|
||||||
for f in fixtures:
|
for f in fixtures_account:
|
||||||
if f['parent_path']:
|
f = prepare_dict(session, f)
|
||||||
parent = Account.get_by_path(session, f['parent_path'])
|
|
||||||
f['parent_account_id'] = parent.id
|
|
||||||
else:
|
|
||||||
f['parent_account_id'] = None
|
|
||||||
del f['parent_path']
|
|
||||||
schema = AccountCreate(**f)
|
schema = AccountCreate(**f)
|
||||||
Account.create(schema)
|
Account.create(schema, session)
|
||||||
|
|
||||||
fixtures = [{
|
for f in fixtures_category:
|
||||||
|
f = prepare_dict(session, f)
|
||||||
|
schema = CategoryCreate(**f)
|
||||||
|
Account.create(schema, session)
|
||||||
|
|
||||||
|
def prepare_dict(session, entry):
|
||||||
|
if entry['parent_path']:
|
||||||
|
parent = Account.get_by_path(session, entry['parent_path'])
|
||||||
|
entry['parent_account_id'] = parent.id
|
||||||
|
else:
|
||||||
|
entry['parent_account_id'] = None
|
||||||
|
del entry['parent_path']
|
||||||
|
return entry
|
||||||
|
|
||||||
|
fixtures_account = [
|
||||||
|
{
|
||||||
"name": "Current Assets",
|
"name": "Current Assets",
|
||||||
"parent_path": None,
|
"parent_path": None,
|
||||||
"type": "Asset"
|
"type": "Asset",
|
||||||
},{
|
"opening_date": date(1970, 1, 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "Cash in Wallet",
|
"name": "Cash in Wallet",
|
||||||
"parent_path": "/Accounts/Asset/",
|
"parent_path": "/Accounts/Asset/Current Assets/",
|
||||||
"type": "Asset",
|
"type": "Asset",
|
||||||
},{
|
"opening_date": date(1970, 1, 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "Checking Account",
|
"name": "Checking Account",
|
||||||
"parent_path": "/Accounts/Asset/",
|
"parent_path": "/Accounts/Asset/Current Assets/",
|
||||||
"type": "Asset",
|
"type": "Asset",
|
||||||
},{
|
"opening_date": date(1970, 1, 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "Savings Account",
|
"name": "Savings Account",
|
||||||
"parent_path": "/Accounts/Asset/",
|
"parent_path": "/Accounts/Asset/Current Assets/",
|
||||||
"type": "Asset",
|
"type": "Asset",
|
||||||
|
"opening_date": date(1970, 1, 1),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Debt Accounts",
|
"name": "Debt Accounts",
|
||||||
"parent_path": None,
|
"parent_path": None,
|
||||||
"type": "Liability",
|
"type": "Liability",
|
||||||
|
"opening_date": date(1970, 1, 1),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Credit Card",
|
"name": "Credit Card",
|
||||||
"parent_path": "/Accounts/Liability/",
|
"parent_path": "/Accounts/Liability/Debt Accounts/",
|
||||||
"type": "Liability",
|
"type": "Liability",
|
||||||
|
"opening_date": date(1970, 1, 1),
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
fixtures_category = [
|
||||||
{
|
{
|
||||||
"name": "Salary",
|
"name": "Salary",
|
||||||
"parent_path": None,
|
"parent_path": None,
|
||||||
@@ -61,12 +85,12 @@ fixtures = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Rent",
|
"name": "Rent",
|
||||||
"parent_path": "/Categories/Expense/Home",
|
"parent_path": "/Categories/Expense/Home/",
|
||||||
"type": "Expense",
|
"type": "Expense",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Electricity",
|
"name": "Electricity",
|
||||||
"parent_path": "/Categories/Expense/Home",
|
"parent_path": "/Categories/Expense/Home/",
|
||||||
"type": "Expense",
|
"type": "Expense",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -78,11 +102,9 @@ fixtures = [{
|
|||||||
"name": "Groceries",
|
"name": "Groceries",
|
||||||
"parent_path": None,
|
"parent_path": None,
|
||||||
"type": "Expense",
|
"type": "Expense",
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
<accounts>
|
<accounts>
|
||||||
<account type="9" name="">
|
<account type="9" name="">
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ class Account(AccountBaseId, table=True):
|
|||||||
sa_relationship_kwargs=dict(remote_side='Account.id')
|
sa_relationship_kwargs=dict(remote_side='Account.id')
|
||||||
)
|
)
|
||||||
children_accounts: list["Account"] = Relationship(back_populates='parent_account')
|
children_accounts: list["Account"] = Relationship(back_populates='parent_account')
|
||||||
|
transaction_splits: list["Split"] = Relationship(back_populates='account')
|
||||||
|
|
||||||
def get_child_path(self):
|
|
||||||
return f"{self.path}{self.name}/"
|
def get_child_path(self, child):
|
||||||
|
return f"{self.path}{child.name}/"
|
||||||
|
|
||||||
def get_root_path(self):
|
def get_root_path(self):
|
||||||
root = "/Categories" if self.is_category() else "/Accounts"
|
root = "/Categories" if self.is_category() else "/Accounts"
|
||||||
return f"{root}/{self.family}/"
|
return f"{root}/{self.family}/{self.name}/"
|
||||||
|
|
||||||
def update_children_path(self, session, old_path):
|
def update_children_path(self, session, old_path):
|
||||||
request = f"UPDATE {self.__tablename__} SET path=REPLACE(path, '{old_path}', '{self.path}') WHERE path LIKE '{old_path}{self.name }/%'"
|
request = f"UPDATE {self.__tablename__} SET path=REPLACE(path, '{old_path}', '{self.path}') WHERE path LIKE '{old_path}{self.name }/%'"
|
||||||
@@ -32,7 +34,7 @@ class Account(AccountBaseId, table=True):
|
|||||||
if not path:
|
if not path:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return session.exec(select(cls).where(cls.path == path))
|
return session.exec(select(cls).where(cls.path == path)).first()
|
||||||
|
|
||||||
|
|
||||||
def get_parent(self, session):
|
def get_parent(self, session):
|
||||||
@@ -47,15 +49,15 @@ class Account(AccountBaseId, table=True):
|
|||||||
self.path = self.get_root_path()
|
self.path = self.get_root_path()
|
||||||
else:
|
else:
|
||||||
self.parent_account = self.get(session, self.parent_account_id)
|
self.parent_account = self.get(session, self.parent_account_id)
|
||||||
self.path = self.parent_account.get_child_path()
|
self.path = self.parent_account.get_child_path(self)
|
||||||
|
|
||||||
return self.path
|
return self.path
|
||||||
|
|
||||||
def compute_family(self):
|
def compute_family(self):
|
||||||
if self.type in Asset:
|
if self.type in Asset:
|
||||||
self.family = AccountFamily.Asset
|
self.family = AccountFamily.Asset.value
|
||||||
elif self.type in Liability:
|
elif self.type in Liability:
|
||||||
self.family = AccountFamily.Liability
|
self.family = AccountFamily.Liability.value
|
||||||
else:
|
else:
|
||||||
self.family = self.type
|
self.family = self.type
|
||||||
return self.family
|
return self.family
|
||||||
@@ -74,7 +76,7 @@ class Account(AccountBaseId, table=True):
|
|||||||
|
|
||||||
model.compute_family()
|
model.compute_family()
|
||||||
model.validate_parent(session)
|
model.validate_parent(session)
|
||||||
model.path = model.get_path(session)
|
model.compute_path(session)
|
||||||
return model
|
return model
|
||||||
|
|
||||||
def validate_parent(self, session):
|
def validate_parent(self, session):
|
||||||
@@ -88,7 +90,7 @@ class Account(AccountBaseId, table=True):
|
|||||||
if parent.family != self.family:
|
if parent.family != self.family:
|
||||||
raise ValueError("Account family mismatch with parent account..")
|
raise ValueError("Account family mismatch with parent account..")
|
||||||
|
|
||||||
if parent.path.startswith(self.path):
|
if self.path and parent.path.startswith(self.path):
|
||||||
raise ValueError("Parent Account is descendant")
|
raise ValueError("Parent Account is descendant")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -114,9 +116,15 @@ class Account(AccountBaseId, table=True):
|
|||||||
|
|
||||||
return account_db
|
return account_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def select(cls):
|
||||||
|
return select(Account)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def list(cls, filters):
|
def list(cls, filters):
|
||||||
return filters.sort(filters.filter(select(Account)))
|
return filters.sort(filters.filter(
|
||||||
|
cls.select()
|
||||||
|
))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def list_accounts(cls, filters):
|
def list_accounts(cls, filters):
|
||||||
|
|||||||
144
api/app/account/resource.py
Normal file
144
api/app/account/resource.py
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
class AccountResource:
|
||||||
|
@classmethod
|
||||||
|
def get_by_path(cls, session, path):
|
||||||
|
if not path:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return session.exec(select(cls).where(cls.path == path)).first()
|
||||||
|
|
||||||
|
def get_parent(self, session):
|
||||||
|
if self.parent_account_id is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.parent_account = self.get(session, self.parent_account_id)
|
||||||
|
return self.parent_account
|
||||||
|
|
||||||
|
def compute_path(self, session):
|
||||||
|
if self.parent_account_id is None:
|
||||||
|
self.path = self.get_root_path()
|
||||||
|
else:
|
||||||
|
self.parent_account = self.get(session, self.parent_account_id)
|
||||||
|
self.path = self.parent_account.get_child_path(self)
|
||||||
|
|
||||||
|
return self.path
|
||||||
|
|
||||||
|
def compute_family(self):
|
||||||
|
if self.type in Asset:
|
||||||
|
self.family = AccountFamily.Asset.value
|
||||||
|
elif self.type in Liability:
|
||||||
|
self.family = AccountFamily.Liability.value
|
||||||
|
else:
|
||||||
|
self.family = self.type
|
||||||
|
return self.family
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def schema_to_model(cls, session, schema, model=None):
|
||||||
|
try:
|
||||||
|
if model:
|
||||||
|
model = cls.model_validate(model, update=schema)
|
||||||
|
else:
|
||||||
|
schema.path = ""
|
||||||
|
schema.family = ""
|
||||||
|
model = cls.model_validate(schema)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
model.compute_family()
|
||||||
|
model.validate_parent(session)
|
||||||
|
model.compute_path(session)
|
||||||
|
return model
|
||||||
|
|
||||||
|
def validate_parent(self, session):
|
||||||
|
if self.parent_account_id is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
parent = self.get_parent(session)
|
||||||
|
if not parent:
|
||||||
|
raise KeyError("Parent account not found.")
|
||||||
|
|
||||||
|
if parent.family != self.family:
|
||||||
|
raise ValueError("Account family mismatch with parent account..")
|
||||||
|
|
||||||
|
if self.path and parent.path.startswith(self.path):
|
||||||
|
raise ValueError("Parent Account is descendant")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, account, session):
|
||||||
|
account_db = cls.schema_to_model(session, account)
|
||||||
|
session.add(account_db)
|
||||||
|
session.flush()
|
||||||
|
session.refresh(account_db)
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
session.refresh(account_db)
|
||||||
|
|
||||||
|
return account_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_equity_account(cls, session):
|
||||||
|
account_db = Account(name="Equity", family="Equity", type="Equity", path="/Equity/")
|
||||||
|
session.add(account_db)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(account_db)
|
||||||
|
|
||||||
|
return account_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def select(cls):
|
||||||
|
return select(Account)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list(cls, filters):
|
||||||
|
return filters.sort(filters.filter(
|
||||||
|
cls.select()
|
||||||
|
))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_accounts(cls, filters):
|
||||||
|
return cls.list(filters).where(
|
||||||
|
Account.family.in_(["Asset", "Liability"])
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_assets(cls, filters):
|
||||||
|
return cls.list(filters).where(Account.family == "Asset")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_liabilities(cls, filters):
|
||||||
|
return cls.list(filters).where(Account.family == "Liability")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_categories(cls, filters):
|
||||||
|
return cls.list(filters).where(
|
||||||
|
Account.type.in_(["Expense", "Income"])
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_expenses(cls, filters):
|
||||||
|
return cls.list(filters).where(Account.family == "Expense")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_incomes(cls, filters):
|
||||||
|
return cls.list(filters).where(Account.family == "Income")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, session, account_id):
|
||||||
|
return session.get(Account, account_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, session, account_db, account_data):
|
||||||
|
previous_path = account_db.path
|
||||||
|
account_db.sqlmodel_update(cls.schema_to_model(session, account_data, account_db))
|
||||||
|
if previous_path != account_db.path or account_data['name'] != account_db.name:
|
||||||
|
account_db.update_children_path(session, previous_path)
|
||||||
|
session.add(account_db)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(account_db)
|
||||||
|
return account_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, session, account):
|
||||||
|
session.delete(account)
|
||||||
|
session.commit()
|
||||||
@@ -20,14 +20,12 @@ class AccountBaseId(AccountBase):
|
|||||||
path: str = Field(index=True)
|
path: str = Field(index=True)
|
||||||
|
|
||||||
class AccountRead(AccountBaseId):
|
class AccountRead(AccountBaseId):
|
||||||
opening_date: date = Field()
|
pass#opening_date: date = Field()
|
||||||
opening_balance: Decimal = Field(decimal_places=2, default=0)
|
#opening_balance: Decimal = Field(decimal_places=2, default=0)
|
||||||
|
|
||||||
class BaseAccountWrite(AccountBase):
|
class BaseAccountWrite(AccountBase):
|
||||||
path: SkipJsonSchema[str] = Field(default="")
|
path: SkipJsonSchema[str] = Field(default="")
|
||||||
family: SkipJsonSchema[str] = Field(default="")
|
family: SkipJsonSchema[str] = Field(default="")
|
||||||
opening_date: date = Field()
|
|
||||||
opening_balance: Decimal = Field(decimal_places=2, default=0)
|
|
||||||
|
|
||||||
class AccountWrite(BaseAccountWrite):
|
class AccountWrite(BaseAccountWrite):
|
||||||
type: Asset | Liability = Field()
|
type: Asset | Liability = Field()
|
||||||
@@ -40,6 +38,8 @@ class AccountWrite(BaseAccountWrite):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
opening_date: date = Field()
|
||||||
|
opening_balance: Decimal = Field(decimal_places=2, default=0)
|
||||||
|
|
||||||
class AccountCreate(AccountWrite):
|
class AccountCreate(AccountWrite):
|
||||||
pass
|
pass
|
||||||
@@ -52,6 +52,17 @@ class CategoryRead(AccountBaseId):
|
|||||||
|
|
||||||
class CategoryWrite(BaseAccountWrite):
|
class CategoryWrite(BaseAccountWrite):
|
||||||
type: CategoryFamily = Field()
|
type: CategoryFamily = Field()
|
||||||
|
parent_account_id: UUID | None = PydField(default=None, json_schema_extra={
|
||||||
|
"foreign_key": {
|
||||||
|
"reference": {
|
||||||
|
"resource": "categories",
|
||||||
|
"schema": "CategoryRead",
|
||||||
|
"label": "name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
opening_date: date = date(1970, 1, 1)
|
||||||
|
opening_balance: Decimal = 0
|
||||||
|
|
||||||
class CategoryCreate(CategoryWrite):
|
class CategoryCreate(CategoryWrite):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ def create_db_and_tables():
|
|||||||
SQLModel.metadata.create_all(engine)
|
SQLModel.metadata.create_all(engine)
|
||||||
|
|
||||||
def drop_tables():
|
def drop_tables():
|
||||||
SQLModel.metadata.create_all(engine)
|
SQLModel.metadata.drop_all(engine)
|
||||||
|
|
||||||
def get_session() -> Session:
|
def get_session() -> Session:
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class TransactionUpdate(TransactionWrite):
|
|||||||
|
|
||||||
class Split(SplitBaseId, table=True):
|
class Split(SplitBaseId, table=True):
|
||||||
transaction: Transaction = Relationship(back_populates="splits")
|
transaction: Transaction = Relationship(back_populates="splits")
|
||||||
account: Account | None = Relationship()
|
account: Account | None = Relationship(back_populates="transaction_splits")
|
||||||
payee: Payee | None = Relationship()
|
payee: Payee | None = Relationship()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user