Changing Account/category structure and Adding transactions and splits
This commit is contained in:
@@ -1,11 +1,7 @@
|
|||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from sqlmodel import Field, SQLModel, select, Relationship
|
from sqlmodel import Field, SQLModel, select
|
||||||
from pydantic import Field as PydField
|
|
||||||
|
|
||||||
from category.models import CategoryRead, Category
|
|
||||||
|
|
||||||
|
|
||||||
class AccountType(Enum):
|
class AccountType(Enum):
|
||||||
Asset = "Asset" # < Denotes a generic asset account.
|
Asset = "Asset" # < Denotes a generic asset account.
|
||||||
@@ -21,24 +17,21 @@ class AccountType(Enum):
|
|||||||
MoneyMarket = "MoneyMarket" # < Money Market Account
|
MoneyMarket = "MoneyMarket" # < Money Market Account
|
||||||
|
|
||||||
Currency = "Currency" # < Denotes a currency trading account.
|
Currency = "Currency" # < Denotes a currency trading account.
|
||||||
Income = "Income" # < Denotes an income account
|
|
||||||
Expense = "Expense" # < Denotes an expense account
|
|
||||||
AssetLoan = "AssetLoan" # < Denotes a loan (asset of the owner of this object)
|
AssetLoan = "AssetLoan" # < Denotes a loan (asset of the owner of this object)
|
||||||
Stock = "Stock" # < Denotes an security account as sub-account for an investment
|
Stock = "Stock" # < Denotes an security account as sub-account for an investment
|
||||||
Equity = "Equity" # < Denotes an equity account e.g. opening/closing balance
|
Equity = "Equity" # < Denotes an equity account e.g. opening/closing balance
|
||||||
|
|
||||||
Payee = "Payee"
|
Income = "Income" # < Denotes an income account
|
||||||
|
Expense = "Expense" # < Denotes an expense account
|
||||||
|
|
||||||
class AccountBase(SQLModel):
|
class AccountBase(SQLModel):
|
||||||
name: str = Field(index=True)
|
name: str = Field(index=True)
|
||||||
type: AccountType = Field(index=True)
|
type: AccountType = Field(index=True)
|
||||||
default_category_id: UUID | None = Field(default=None, foreign_key="category.id")
|
|
||||||
|
|
||||||
class AccountBaseId(AccountBase):
|
class AccountBaseId(AccountBase):
|
||||||
id: UUID | None = Field(default_factory=uuid4, primary_key=True)
|
id: UUID | None = Field(default_factory=uuid4, primary_key=True)
|
||||||
|
|
||||||
class Account(AccountBaseId, table=True):
|
class Account(AccountBaseId, table=True):
|
||||||
default_category: Category | None = Relationship()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, account, session):
|
def create(cls, account, session):
|
||||||
@@ -51,7 +44,7 @@ class Account(AccountBaseId, table=True):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def list(cls):
|
def list(cls):
|
||||||
return select(Account).join(Category)
|
return select(Account)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, session, account_id):
|
def get(cls, session, account_id):
|
||||||
@@ -71,18 +64,10 @@ class Account(AccountBaseId, table=True):
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
class AccountRead(AccountBaseId):
|
class AccountRead(AccountBaseId):
|
||||||
default_category: CategoryRead
|
pass
|
||||||
|
|
||||||
class AccountWrite(AccountBase):
|
class AccountWrite(AccountBase):
|
||||||
default_category_id: UUID = PydField(default=None, json_schema_extra={
|
pass
|
||||||
"foreign_key": {
|
|
||||||
"reference": {
|
|
||||||
"resource": "categories",
|
|
||||||
"schema": "CategoryRead",
|
|
||||||
"label": "name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
class AccountCreate(AccountWrite):
|
class AccountCreate(AccountWrite):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
from uuid import UUID, uuid4
|
|
||||||
from enum import Enum
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi_filter.contrib.sqlalchemy import Filter
|
|
||||||
from sqlmodel import Field, SQLModel, select
|
|
||||||
|
|
||||||
class CategoryBase(SQLModel):
|
|
||||||
name: str = Field(index=True)
|
|
||||||
|
|
||||||
class CategoryRead(CategoryBase):
|
|
||||||
id: UUID | None = Field(default_factory=uuid4, primary_key=True)
|
|
||||||
|
|
||||||
class Category(CategoryRead, table=True):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create(cls, category, session):
|
|
||||||
category_db = cls.model_validate(category)
|
|
||||||
session.add(category_db)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(category_db)
|
|
||||||
|
|
||||||
return category_db
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def list(cls, filters):
|
|
||||||
return filters.filter(select(cls))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, session, category_id):
|
|
||||||
return session.get(Category, category_id)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def update(cls, session, category_db, category_data):
|
|
||||||
category_db.sqlmodel_update(category_data)
|
|
||||||
session.add(category_db)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(category_db)
|
|
||||||
return category_db
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def delete(cls, session, category):
|
|
||||||
session.delete(category)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
class CategoryWrite(CategoryBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class CategoryCreate(CategoryWrite):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class CategoryUpdate(CategoryWrite):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class CategoryFilters(Filter):
|
|
||||||
name__like: Optional[str] = None
|
|
||||||
|
|
||||||
class Constants(Filter.Constants):
|
|
||||||
model = Category
|
|
||||||
search_model_fields = ["name"]
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, Depends
|
|
||||||
from fastapi_filter import FilterDepends
|
|
||||||
from fastapi_pagination import Page
|
|
||||||
from fastapi_pagination.ext.sqlmodel import paginate
|
|
||||||
|
|
||||||
from category.models import Category, CategoryCreate, CategoryRead, CategoryUpdate, CategoryFilters
|
|
||||||
from db import SessionDep
|
|
||||||
from user.manager import get_current_user
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
@router.post("")
|
|
||||||
def create_category(category: CategoryCreate, session: SessionDep, current_user=Depends(get_current_user)) -> CategoryRead:
|
|
||||||
Category.create(category, session)
|
|
||||||
return category
|
|
||||||
|
|
||||||
@router.get("")
|
|
||||||
def read_categories(session: SessionDep,
|
|
||||||
filters: CategoryFilters = FilterDepends(CategoryFilters),
|
|
||||||
current_user=Depends(get_current_user)) -> Page[CategoryRead]:
|
|
||||||
return paginate(session, Category.list(filters))
|
|
||||||
|
|
||||||
@router.get("/{category_id}")
|
|
||||||
def read_category(category_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> CategoryRead:
|
|
||||||
category = Category.get(session, category_id)
|
|
||||||
if not category:
|
|
||||||
raise HTTPException(status_code=404, detail="Category not found")
|
|
||||||
return category
|
|
||||||
|
|
||||||
@router.put("/{category_id}")
|
|
||||||
def update_category(category_id: UUID, category: CategoryUpdate, session: SessionDep, current_user=Depends(get_current_user)) -> CategoryRead:
|
|
||||||
db_category = Category.get(session, category_id)
|
|
||||||
if not db_category:
|
|
||||||
raise HTTPException(status_code=404, detail="Category not found")
|
|
||||||
|
|
||||||
category_data = category.model_dump(exclude_unset=True)
|
|
||||||
category = Category.update(session, db_category, category_data)
|
|
||||||
return category
|
|
||||||
|
|
||||||
@router.delete("/{category_id}")
|
|
||||||
def delete_category(category_id: UUID, session: SessionDep, current_user=Depends(get_current_user)):
|
|
||||||
category = Category.get(session, category_id)
|
|
||||||
if not category:
|
|
||||||
raise HTTPException(status_code=404, detail="Category not found")
|
|
||||||
|
|
||||||
Category.delete(session, category)
|
|
||||||
return {"ok": True}
|
|
||||||
@@ -9,7 +9,8 @@ from fastapi_pagination import add_pagination
|
|||||||
from db import create_db_and_tables
|
from db import create_db_and_tables
|
||||||
from user import user_router, auth_router, create_admin_account
|
from user import user_router, auth_router, create_admin_account
|
||||||
from account.routes import router as account_router
|
from account.routes import router as account_router
|
||||||
from category.routes import router as category_router
|
from payee.routes import router as payee_router
|
||||||
|
from transaction.routes import router as transaction_router
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
@@ -38,7 +39,8 @@ app.add_middleware(
|
|||||||
app.include_router(auth_router, prefix="/auth", tags=["auth"], )
|
app.include_router(auth_router, prefix="/auth", tags=["auth"], )
|
||||||
app.include_router(user_router, prefix="/users", tags=["users"])
|
app.include_router(user_router, prefix="/users", tags=["users"])
|
||||||
app.include_router(account_router, prefix="/accounts", tags=["accounts"])
|
app.include_router(account_router, prefix="/accounts", tags=["accounts"])
|
||||||
app.include_router(category_router, prefix="/categories", tags=["categories"])
|
app.include_router(payee_router, prefix="/payees", tags=["payees"])
|
||||||
|
app.include_router(transaction_router, prefix="/transactions", tags=["transactions"])
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||||
|
|
||||||
|
|||||||
76
api/app/payee/models.py
Normal file
76
api/app/payee/models.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from uuid import UUID, uuid4
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi_filter.contrib.sqlalchemy import Filter
|
||||||
|
from sqlmodel import Field, SQLModel, select, Relationship
|
||||||
|
from pydantic import Field as PydField
|
||||||
|
|
||||||
|
from account.models import Account, AccountRead
|
||||||
|
|
||||||
|
|
||||||
|
class PayeeBase(SQLModel):
|
||||||
|
name: str = Field(index=True)
|
||||||
|
default_account_id: UUID | None = Field(default=None, foreign_key="account.id")
|
||||||
|
|
||||||
|
class PayeeBaseId(PayeeBase):
|
||||||
|
id: UUID | None = Field(default_factory=uuid4, primary_key=True)
|
||||||
|
|
||||||
|
class Payee(PayeeBaseId, table=True):
|
||||||
|
default_account: Account | None = Relationship()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, payee, session):
|
||||||
|
payee_db = cls.model_validate(payee)
|
||||||
|
session.add(payee_db)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(payee_db)
|
||||||
|
|
||||||
|
return payee_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list(cls, filters):
|
||||||
|
return filters.filter(select(cls)).join(Account)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, session, payee_id):
|
||||||
|
return session.get(Payee, payee_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, session, payee_db, payee_data):
|
||||||
|
payee_db.sqlmodel_update(payee_data)
|
||||||
|
session.add(payee_db)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(payee_db)
|
||||||
|
return payee_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, session, payee):
|
||||||
|
session.delete(payee)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
class PayeeRead(PayeeBaseId):
|
||||||
|
default_account: AccountRead
|
||||||
|
|
||||||
|
class PayeeWrite(PayeeBase):
|
||||||
|
default_account_id: UUID = PydField(default=None, json_schema_extra={
|
||||||
|
"foreign_key": {
|
||||||
|
"reference": {
|
||||||
|
"resource": "accounts",
|
||||||
|
"schema": "AccountRead",
|
||||||
|
"label": "name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
class PayeeCreate(PayeeWrite):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PayeeUpdate(PayeeWrite):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PayeeFilters(Filter):
|
||||||
|
name__like: Optional[str] = None
|
||||||
|
|
||||||
|
class Constants(Filter.Constants):
|
||||||
|
model = Payee
|
||||||
|
search_model_fields = ["name"]
|
||||||
49
api/app/payee/routes.py
Normal file
49
api/app/payee/routes.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
from fastapi_filter import FilterDepends
|
||||||
|
from fastapi_pagination import Page
|
||||||
|
from fastapi_pagination.ext.sqlmodel import paginate
|
||||||
|
|
||||||
|
from payee.models import Payee, PayeeCreate, PayeeRead, PayeeUpdate, PayeeFilters
|
||||||
|
from db import SessionDep
|
||||||
|
from user.manager import get_current_user
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post("")
|
||||||
|
def create_payee(payee: PayeeCreate, session: SessionDep, current_user=Depends(get_current_user)) -> PayeeRead:
|
||||||
|
result = Payee.create(payee, session)
|
||||||
|
return Payee.get(session, result.id)
|
||||||
|
|
||||||
|
@router.get("")
|
||||||
|
def read_categories(session: SessionDep,
|
||||||
|
filters: PayeeFilters = FilterDepends(PayeeFilters),
|
||||||
|
current_user=Depends(get_current_user)) -> Page[PayeeRead]:
|
||||||
|
return paginate(session, Payee.list(filters))
|
||||||
|
|
||||||
|
@router.get("/{payee_id}")
|
||||||
|
def read_payee(payee_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> PayeeRead:
|
||||||
|
payee = Payee.get(session, payee_id)
|
||||||
|
if not payee:
|
||||||
|
raise HTTPException(status_code=404, detail="Payee not found")
|
||||||
|
return payee
|
||||||
|
|
||||||
|
@router.put("/{payee_id}")
|
||||||
|
def update_payee(payee_id: UUID, payee: PayeeUpdate, session: SessionDep, current_user=Depends(get_current_user)) -> PayeeRead:
|
||||||
|
db_payee = Payee.get(session, payee_id)
|
||||||
|
if not db_payee:
|
||||||
|
raise HTTPException(status_code=404, detail="Payee not found")
|
||||||
|
|
||||||
|
payee_data = payee.model_dump(exclude_unset=True)
|
||||||
|
payee = Payee.update(session, db_payee, payee_data)
|
||||||
|
return payee
|
||||||
|
|
||||||
|
@router.delete("/{payee_id}")
|
||||||
|
def delete_payee(payee_id: UUID, session: SessionDep, current_user=Depends(get_current_user)):
|
||||||
|
payee = Payee.get(session, payee_id)
|
||||||
|
if not payee:
|
||||||
|
raise HTTPException(status_code=404, detail="Payee not found")
|
||||||
|
|
||||||
|
Payee.delete(session, payee)
|
||||||
|
return {"ok": True}
|
||||||
0
api/app/transaction/__init__.py
Normal file
0
api/app/transaction/__init__.py
Normal file
127
api/app/transaction/models.py
Normal file
127
api/app/transaction/models.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
from decimal import Decimal
|
||||||
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
from sqlmodel import Field, SQLModel, select, Relationship
|
||||||
|
from pydantic import Field as PydField
|
||||||
|
|
||||||
|
from account.models import Account, AccountRead
|
||||||
|
from payee.models import Payee, PayeeRead
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionBase(SQLModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TransactionBaseId(TransactionBase):
|
||||||
|
id: UUID | None = Field(default_factory=uuid4, primary_key=True)
|
||||||
|
|
||||||
|
class Transaction(TransactionBaseId, table=True):
|
||||||
|
splits: list["Split"] = Relationship(back_populates="transaction")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, transaction, session):
|
||||||
|
transaction_db = cls.model_validate(transaction)
|
||||||
|
session.add(transaction_db)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(transaction_db)
|
||||||
|
|
||||||
|
return transaction_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list(cls):
|
||||||
|
return select(Transaction).join(Split).join(Account)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, session, transaction_id):
|
||||||
|
return session.get(Transaction, transaction_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, session, transaction_db, transaction_data):
|
||||||
|
transaction_db.sqlmodel_update(transaction_data)
|
||||||
|
session.add(transaction_db)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(transaction_db)
|
||||||
|
return transaction_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, session, transaction):
|
||||||
|
session.delete(transaction)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
class SplitBase(SQLModel):
|
||||||
|
account_id: UUID = Field(foreign_key="account.id")
|
||||||
|
payee_id: UUID = Field(foreign_key="payee.id")
|
||||||
|
amount: Decimal = Field(decimal_places=2)
|
||||||
|
|
||||||
|
class SplitBaseId(SplitBase):
|
||||||
|
transaction_id: UUID = Field(primary_key=True, foreign_key="transaction.id")
|
||||||
|
id: int = Field(primary_key=True)
|
||||||
|
|
||||||
|
class SplitRead(SplitBaseId):
|
||||||
|
account: AccountRead
|
||||||
|
payee: PayeeRead
|
||||||
|
|
||||||
|
class TransactionRead(TransactionBaseId):
|
||||||
|
splits: list[SplitRead]
|
||||||
|
|
||||||
|
class SplitWrite(SplitBase):
|
||||||
|
account_id: UUID = PydField(json_schema_extra={
|
||||||
|
"foreign_key": {
|
||||||
|
"reference": {
|
||||||
|
"resource": "accounts",
|
||||||
|
"schema": "AccountRead",
|
||||||
|
"label": "name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
payee_id: UUID = PydField(json_schema_extra={
|
||||||
|
"foreign_key": {
|
||||||
|
"reference": {
|
||||||
|
"resource": "payees",
|
||||||
|
"schema": "PayeeRead",
|
||||||
|
"label": "name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
class TransactionWrite(TransactionBase):
|
||||||
|
splits: list[SplitWrite] = Field()
|
||||||
|
|
||||||
|
class TransactionCreate(TransactionWrite):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TransactionUpdate(TransactionWrite):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Split(SplitBaseId, table=True):
|
||||||
|
transaction: Transaction = Relationship(back_populates="splits")
|
||||||
|
account: Account | None = Relationship()
|
||||||
|
payee: Payee | None = Relationship()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, transaction_split, session):
|
||||||
|
transaction_split_db = cls.model_validate(transaction_split)
|
||||||
|
session.add(transaction_split_db)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(transaction_split_db)
|
||||||
|
|
||||||
|
return transaction_split_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, session, transaction_db, transaction_data):
|
||||||
|
transaction_db.sqlmodel_update(transaction_data)
|
||||||
|
session.add(transaction_db)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(transaction_db)
|
||||||
|
return transaction_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, session, transaction):
|
||||||
|
session.delete(transaction)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
class SplitCreate(SplitWrite):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SplitUpdate(SplitWrite):
|
||||||
|
pass
|
||||||
46
api/app/transaction/routes.py
Normal file
46
api/app/transaction/routes.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
from fastapi_pagination import Page
|
||||||
|
from fastapi_pagination.ext.sqlmodel import paginate
|
||||||
|
|
||||||
|
from transaction.models import Transaction, TransactionCreate, TransactionRead, TransactionUpdate
|
||||||
|
from db import SessionDep
|
||||||
|
from user.manager import get_current_user
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post("")
|
||||||
|
def create_transaction(transaction: TransactionCreate, session: SessionDep, current_user=Depends(get_current_user)) -> TransactionRead:
|
||||||
|
result = Transaction.create(transaction, session)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@router.get("")
|
||||||
|
def read_transactions(session: SessionDep, current_user=Depends(get_current_user)) -> Page[TransactionRead]:
|
||||||
|
return paginate(session, Transaction.list())
|
||||||
|
|
||||||
|
@router.get("/{transaction_id}")
|
||||||
|
def read_transaction(transaction_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> TransactionRead:
|
||||||
|
transaction = Transaction.get(session, transaction_id)
|
||||||
|
if not transaction:
|
||||||
|
raise HTTPException(status_code=404, detail="Transaction not found")
|
||||||
|
return transaction
|
||||||
|
|
||||||
|
@router.put("/{transaction_id}")
|
||||||
|
def update_transaction(transaction_id: UUID, transaction: TransactionUpdate, session: SessionDep, current_user=Depends(get_current_user)) -> TransactionRead:
|
||||||
|
db_transaction = Transaction.get(session, transaction_id)
|
||||||
|
if not db_transaction:
|
||||||
|
raise HTTPException(status_code=404, detail="Transaction not found")
|
||||||
|
|
||||||
|
transaction_data = transaction.model_dump(exclude_unset=True)
|
||||||
|
transaction = Transaction.update(session, db_transaction, transaction_data)
|
||||||
|
return transaction
|
||||||
|
|
||||||
|
@router.delete("/{transaction_id}")
|
||||||
|
def delete_transaction(transaction_id: UUID, session: SessionDep, current_user=Depends(get_current_user)):
|
||||||
|
transaction = Transaction.get(session, transaction_id)
|
||||||
|
if not transaction:
|
||||||
|
raise HTTPException(status_code=404, detail="Transaction not found")
|
||||||
|
|
||||||
|
Transaction.delete(session, transaction)
|
||||||
|
return {"ok": True}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Refine, type AuthProvider, Authenticated } from "@refinedev/core";
|
import { Refine, Authenticated } from "@refinedev/core";
|
||||||
import {
|
import {
|
||||||
ThemedLayoutV2,
|
ThemedLayoutV2,
|
||||||
ErrorComponent,
|
ErrorComponent,
|
||||||
@@ -13,7 +13,6 @@ import FormControlLabel from "@mui/material/FormControlLabel";
|
|||||||
import Checkbox from "@mui/material/Checkbox";
|
import Checkbox from "@mui/material/Checkbox";
|
||||||
import { ThemeProvider } from "@mui/material/styles";
|
import { ThemeProvider } from "@mui/material/styles";
|
||||||
import { authProvider } from "./providers/auth-provider";
|
import { authProvider } from "./providers/auth-provider";
|
||||||
//import dataProvider from "@refinedev/simple-rest";
|
|
||||||
import { dataProvider } from "./providers/data-provider";
|
import { dataProvider } from "./providers/data-provider";
|
||||||
import routerProvider, {
|
import routerProvider, {
|
||||||
NavigateToResource,
|
NavigateToResource,
|
||||||
@@ -24,9 +23,10 @@ import routerProvider, {
|
|||||||
import { BrowserRouter, Routes, Route, Outlet } from "react-router";
|
import { BrowserRouter, Routes, Route, Outlet } from "react-router";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
import { PostList, PostCreate, PostEdit } from "../src/pages/posts";
|
import { PostList, PostCreate, PostEdit } from "./pages/posts";
|
||||||
import { AccountList, AccountCreate, AccountEdit } from "../src/pages/accounts";
|
import { AccountList, AccountCreate, AccountEdit } from "./pages/accounts";
|
||||||
import { CategoryList, CategoryCreate, CategoryEdit } from "../src/pages/categories";
|
import { PayeeCreate, PayeeEdit, PayeeList } from "./pages/payees";
|
||||||
|
import { TransactionCreate, TransactionEdit, TransactionList } from "./pages/transactions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mock auth credentials to simulate authentication
|
* mock auth credentials to simulate authentication
|
||||||
@@ -82,10 +82,16 @@ const App: React.FC = () => {
|
|||||||
create: "/accounts/create",
|
create: "/accounts/create",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "categories",
|
name: "payees",
|
||||||
list: "/categories",
|
list: "/payees",
|
||||||
edit: "/categories/edit/:id",
|
edit: "/payees/edit/:id",
|
||||||
create: "/categories/create",
|
create: "/payees/create",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "transactions",
|
||||||
|
list: "/transactions",
|
||||||
|
edit: "/transactions/edit/:id",
|
||||||
|
create: "/transactions/create",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
options={{
|
options={{
|
||||||
@@ -123,10 +129,15 @@ const App: React.FC = () => {
|
|||||||
<Route path="create" element={<AccountCreate />} />
|
<Route path="create" element={<AccountCreate />} />
|
||||||
<Route path="edit/:id" element={<AccountEdit />} />
|
<Route path="edit/:id" element={<AccountEdit />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/categories">
|
<Route path="/payees">
|
||||||
<Route index element={<CategoryList />} />
|
<Route index element={<PayeeList />} />
|
||||||
<Route path="create" element={<CategoryCreate />} />
|
<Route path="create" element={<PayeeCreate />} />
|
||||||
<Route path="edit/:id" element={<CategoryEdit />} />
|
<Route path="edit/:id" element={<PayeeEdit />} />
|
||||||
|
</Route>
|
||||||
|
<Route path="/transactions">
|
||||||
|
<Route index element={<TransactionList />} />
|
||||||
|
<Route path="create" element={<TransactionCreate />} />
|
||||||
|
<Route path="edit/:id" element={<TransactionEdit />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import {CrudForm} from "../../common/crud/crud-form";
|
|
||||||
|
|
||||||
|
|
||||||
export const CategoryCreate: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<CrudForm
|
|
||||||
schemaName={"CategoryCreate"}
|
|
||||||
resource={"categories"}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
11
gui/app/src/pages/payees/create.tsx
Normal file
11
gui/app/src/pages/payees/create.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import {CrudForm} from "../../common/crud/crud-form";
|
||||||
|
|
||||||
|
|
||||||
|
export const PayeeCreate: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<CrudForm
|
||||||
|
schemaName={"PayeeCreate"}
|
||||||
|
resource={"payees"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,13 +2,13 @@ import { CrudForm } from "../../common/crud/crud-form";
|
|||||||
import { useParams } from "react-router"
|
import { useParams } from "react-router"
|
||||||
|
|
||||||
|
|
||||||
export const CategoryEdit: React.FC = () => {
|
export const PayeeEdit: React.FC = () => {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CrudForm
|
<CrudForm
|
||||||
schemaName={"CategoryUpdate"}
|
schemaName={"PayeeUpdate"}
|
||||||
resource={"categories"}
|
resource={"payees"}
|
||||||
id={id}
|
id={id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -6,7 +6,7 @@ import { DataGrid, type GridColDef } from "@mui/x-data-grid";
|
|||||||
import type { IAccount } from "../../interfaces";
|
import type { IAccount } from "../../interfaces";
|
||||||
import {ButtonGroup} from "@mui/material";
|
import {ButtonGroup} from "@mui/material";
|
||||||
|
|
||||||
export const CategoryList: React.FC = () => {
|
export const PayeeList: React.FC = () => {
|
||||||
const { dataGridProps } = useDataGrid<IAccount>();
|
const { dataGridProps } = useDataGrid<IAccount>();
|
||||||
|
|
||||||
const columns = React.useMemo<GridColDef<IAccount>[]>(
|
const columns = React.useMemo<GridColDef<IAccount>[]>(
|
||||||
11
gui/app/src/pages/transactions/create.tsx
Normal file
11
gui/app/src/pages/transactions/create.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import {CrudForm} from "../../common/crud/crud-form";
|
||||||
|
|
||||||
|
|
||||||
|
export const TransactionCreate: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<CrudForm
|
||||||
|
schemaName={"TransactionCreate"}
|
||||||
|
resource={"transactions"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
15
gui/app/src/pages/transactions/edit.tsx
Normal file
15
gui/app/src/pages/transactions/edit.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { CrudForm } from "../../common/crud/crud-form";
|
||||||
|
import { useParams } from "react-router"
|
||||||
|
|
||||||
|
|
||||||
|
export const TransactionEdit: React.FC = () => {
|
||||||
|
const { id } = useParams()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CrudForm
|
||||||
|
schemaName={"TransactionUpdate"}
|
||||||
|
resource={"transactions"}
|
||||||
|
id={id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
3
gui/app/src/pages/transactions/index.tsx
Normal file
3
gui/app/src/pages/transactions/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./list";
|
||||||
|
export * from "./create";
|
||||||
|
export * from "./edit";
|
||||||
48
gui/app/src/pages/transactions/list.tsx
Normal file
48
gui/app/src/pages/transactions/list.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import {DeleteButton, EditButton, List, useDataGrid} from "@refinedev/mui";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { DataGrid, type GridColDef } from "@mui/x-data-grid";
|
||||||
|
|
||||||
|
import type { IAccount } from "../../interfaces";
|
||||||
|
import {ButtonGroup} from "@mui/material";
|
||||||
|
|
||||||
|
export const TransactionList: React.FC = () => {
|
||||||
|
const { dataGridProps } = useDataGrid<IAccount>();
|
||||||
|
|
||||||
|
const columns = React.useMemo<GridColDef<IAccount>[]>(
|
||||||
|
() => [
|
||||||
|
{ field: "id", headerName: "ID" },
|
||||||
|
{ field: "name", headerName: "Name", flex: 1 },
|
||||||
|
{
|
||||||
|
field: "actions",
|
||||||
|
headerName: "Actions",
|
||||||
|
display: "flex",
|
||||||
|
renderCell: function render({ row }) {
|
||||||
|
return (
|
||||||
|
<ButtonGroup>
|
||||||
|
<EditButton hideText recordItemId={row.id} />
|
||||||
|
<DeleteButton hideText recordItemId={row.id} />
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
align: "center",
|
||||||
|
headerAlign: "center",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
maxHeight: "calc(100vh - 320px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DataGrid {...dataGridProps} columns={columns} />
|
||||||
|
</div>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user