Compare commits

...

11 Commits

22 changed files with 415 additions and 103 deletions

View File

@@ -7,13 +7,11 @@ from fastapi_filter.contrib.sqlalchemy import Filter
from fastapi_pagination import Page
from fastapi_pagination.ext.sqlmodel import paginate
from account.schemas import AccountCreate, AccountRead, AccountUpdate
from account.schemas import AccountCreate, AccountRead, AccountUpdate, OpeningTransaction, OpeningTransactionUpdate
from account.models import Account
from account.resource import AccountResource
from db import SessionDep
from transaction.models import TransactionRead
from transaction.resource import TransactionResource
from user.manager import get_current_user
@@ -57,13 +55,6 @@ def read_account(account_id: UUID, session: SessionDep, current_user=Depends(get
raise HTTPException(status_code=404, detail="Account not found")
return account
@router.get("/{account_id}/opening_state")
def read_account_opening_state(account_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> TransactionRead:
transaction = TransactionResource.get_opening_transaction(session, account_id)
if not transaction:
raise HTTPException(status_code=404, detail="Account not found")
return transaction
@router.put("/{account_id}")
def update_account(account_id: UUID, account: AccountUpdate, session: SessionDep, current_user=Depends(get_current_user)) -> AccountRead:
db_account = AccountResource.get(session, account_id)
@@ -74,6 +65,22 @@ def update_account(account_id: UUID, account: AccountUpdate, session: SessionDep
account = AccountResource.update(session, db_account, account_data)
return account
@router.get("/{account_id}/opening_state")
def read_account_opening_state(account_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> OpeningTransaction:
transaction = AccountResource.get_opening_transaction(session, account_id)
if not transaction:
raise HTTPException(status_code=404, detail="Account not found")
return transaction
@router.put("/{account_id}/opening_state")
def update_account_opening_state(account_id: UUID, opening_transaction: OpeningTransactionUpdate, session: SessionDep, current_user=Depends(get_current_user)) -> OpeningTransaction:
account = AccountResource.get(session, account_id)
if not account:
raise HTTPException(status_code=404, detail="Account not found")
transaction = AccountResource.update_opening_transaction(session, account, opening_transaction)
return transaction
@router.delete("/{account_id}")
def delete_account(account_id: UUID, session: SessionDep, current_user=Depends(get_current_user)):
account = AccountResource.get(session, account_id)

View File

@@ -29,42 +29,42 @@ fixtures_account = [
"name": "Current Assets",
"parent_path": None,
"type": "Asset",
"opening_date": date(1970, 1, 1),
"opening_date": date(1970, 1, 2),
"opening_balance": Decimal("0.00"),
},
{
"name": "Cash in Wallet",
"parent_path": "/Accounts/Asset/Current Assets/",
"type": "Asset",
"opening_date": date(1970, 1, 1),
"opening_date": date(1970, 1, 3),
"opening_balance": Decimal("0.00"),
},
{
"name": "Checking Account",
"parent_path": "/Accounts/Asset/Current Assets/",
"type": "Asset",
"opening_date": date(1970, 1, 1),
"opening_date": date(1970, 1, 4),
"opening_balance": Decimal("0.00"),
},
{
"name": "Savings Account",
"parent_path": "/Accounts/Asset/Current Assets/",
"type": "Asset",
"opening_date": date(1970, 1, 1),
"opening_date": date(1970, 1, 5),
"opening_balance": Decimal("0.00"),
},
{
"name": "Debt Accounts",
"parent_path": None,
"type": "Liability",
"opening_date": date(1970, 1, 1),
"opening_date": date(1970, 1, 6),
"opening_balance": Decimal("0.00"),
},
{
"name": "Credit Card",
"parent_path": "/Accounts/Liability/Debt Accounts/",
"type": "Liability",
"opening_date": date(1970, 1, 1),
"opening_date": date(1970, 1, 7),
"opening_balance": Decimal("0.00"),
},
]

View File

@@ -1,9 +1,13 @@
from sqlalchemy import literal_column
from datetime import date
from sqlalchemy import and_
from sqlalchemy.orm import aliased
from sqlmodel import select
from account.models import Account
from account.schemas import OpeningTransaction
from transaction.models import Split, Transaction
from transaction.resource import TransactionResource
class AccountResource:
@@ -24,9 +28,10 @@ class AccountResource:
@classmethod
def create_opening_transaction(cls, session, account, schema):
equity_account = cls.get_by_path(session, "/Equity/")
t = Transaction()
t.transaction_date = schema.opening_date
t.sequence = TransactionResource.get_sequence_number(session)
split_opening = Split()
split_opening.id = 0
split_opening.transaction = t
@@ -41,6 +46,56 @@ class AccountResource:
account.transaction_splits.append(split_opening)
@classmethod
def fetch_opening_transaction(cls, session, account_id):
split_account = aliased(Split)
split_equity = aliased(Split)
account_equity = aliased(Account)
return session.execute(select(Transaction)
.join(split_account, and_(split_account.transaction_id == Transaction.id, split_account.account_id == account_id))
.join(split_equity, split_equity.transaction_id == Transaction.id)
.join(account_equity, and_(account_equity.id == split_equity.account_id, account_equity.path == "/Equity/"))
).first()[0]
@classmethod
def get_opening_transaction(cls, session, account_id):
transaction = cls.fetch_opening_transaction(session, account_id)
if transaction is None:
return None
return OpeningTransaction(
opening_date=transaction.transaction_date,
opening_balance=transaction.splits[0].amount
)
@classmethod
def update_opening_transaction(cls, session, account, schema):
opening_transaction = cls.fetch_opening_transaction(session, account.id)
stmt = select(Transaction).join(Split) \
.where(Transaction.id != opening_transaction.id) \
.where(Split.account_id == account.id) \
.order_by(Transaction.transaction_date.asc())
first_transaction = session.exec(stmt).first()
if first_transaction and schema.opening_date > first_transaction[0].transaction_date:
raise ValueError("Account opening date is posterior to its first transaction date")
opening_transaction = cls.fetch_opening_transaction(session, account.id)
opening_transaction.transaction_date = schema.opening_date
opening_transaction.splits[0].amount = schema.opening_balance
opening_transaction.splits[1].amount = - schema.opening_balance
session.commit()
session.refresh(opening_transaction)
return OpeningTransaction(
opening_date=opening_transaction.transaction_date,
opening_balance=opening_transaction.splits[0].amount
)
@classmethod
def schema_to_model(cls, session, schema, model=None):
try:

View File

@@ -4,7 +4,7 @@ from typing import Optional
from uuid import UUID, uuid4
from sqlmodel import Field, SQLModel
from pydantic import Field as PydField
from pydantic import Field as PydField, BaseModel
from pydantic.json_schema import SkipJsonSchema
from account.enums import Asset, Liability, CategoryFamily
@@ -67,3 +67,10 @@ class CategoryCreate(CategoryWrite):
class CategoryUpdate(CategoryWrite):
pass
class OpeningTransaction(BaseModel):
opening_date: date = Field()
opening_balance: MonetaryAmount = Field(default=0)
class OpeningTransactionUpdate(OpeningTransaction):
pass

View File

@@ -2,25 +2,33 @@ from dataclasses import dataclass
from decimal import Decimal
from typing import Any
from pydantic import GetCoreSchemaHandler
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic_core import core_schema
from pydantic.json_schema import JsonSchemaValue
@dataclass
class MonetaryAmount:
@classmethod
def __get_pydantic_json_schema__(
cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> JsonSchemaValue:
json_schema = handler(schema)
if "anyOf" in json_schema:
for key, value in json_schema["anyOf"][0].items():
json_schema[key] = value
del json_schema["anyOf"]
json_schema["format"] = "monetary"
return json_schema
@classmethod
def __get_pydantic_core_schema__(
cls, source: type[Any], handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
assert source is MonetaryAmount
return core_schema.no_info_after_validator_function(
cls._validate,
core_schema.decimal_schema(multiple_of=0.01),
serialization=core_schema.plain_serializer_function_ser_schema(
cls._serialize,
info_arg=False,
return_schema=core_schema.decimal_schema(multiple_of=0.01),
),
)
return core_schema.decimal_schema(multiple_of=0.01)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@@ -2,6 +2,8 @@ import main
from account.resource import AccountResource
from account.fixtures import inject_fixtures as account_inject_fixtures
from payee.fixtures import inject_fixtures as payee_inject_fixtures
from transaction.fixtures import inject_fixtures as transaction_inject_fixtures
from db import create_db_and_tables, get_session, drop_tables
from user import create_admin_user
@@ -12,3 +14,5 @@ session = get_session().__next__()
create_admin_user(session)
AccountResource.create_equity_account(session)
account_inject_fixtures(session)
payee_inject_fixtures(session)
transaction_inject_fixtures(session)

View File

View File

@@ -0,0 +1,26 @@
from sqlalchemy import and_, select, func
from sqlalchemy.orm import aliased
from transaction.models import Split, Transaction
class LedgerResource:
@classmethod
def get_ledger(cls, account_id, filters):
split_account = aliased(Split)
split_balance = aliased(Split)
transaction_balance = aliased(Transaction)
balance_stmt = select(func.sum(split_balance.amount)) \
.join(transaction_balance) \
.where(and_(
split_balance.account_id == split_account.account_id,
transaction_balance.sequence <= Transaction.sequence)
).scalar_subquery()
stmt = select(Transaction,split_account,balance_stmt.label('balance')) \
.join(
split_account,
and_(Transaction.id == split_account.transaction_id, split_account.account_id == account_id)
)
return filters.filter(stmt)

31
api/app/ledger/routes.py Normal file
View File

@@ -0,0 +1,31 @@
from uuid import UUID
from fastapi import APIRouter, Depends
from fastapi_filter import FilterDepends
from fastapi_filter.contrib.sqlalchemy import Filter
from fastapi_pagination import Page
from fastapi_pagination.ext.sqlalchemy import paginate
from fastapi_pagination.utils import disable_installed_extensions_check
from db import SessionDep
from ledger.resource import LedgerResource
from ledger.schema import TransactionLedgerRead
from transaction.models import Transaction
from user.manager import get_current_user
router = APIRouter()
class LedgerFilters(Filter):
class Constants(Filter.Constants):
model = Transaction
search_model_fields = ["id", "sequence"]
disable_installed_extensions_check()
@router.get("/{account_id}")
def read_ledger(account_id: UUID, session: SessionDep, filters: LedgerFilters = FilterDepends(LedgerFilters), current_user=Depends(get_current_user)) -> Page[TransactionLedgerRead]:
return paginate(
session,
LedgerResource.get_ledger(account_id, filters),
transformer=lambda items: [TransactionLedgerRead(transaction=transaction, account_split=split, balance=balance) for transaction, split, balance in items],
)

10
api/app/ledger/schema.py Normal file
View File

@@ -0,0 +1,10 @@
import decimal
from pydantic import BaseModel, Field
from transaction.models import TransactionRead, SplitRead
class TransactionLedgerRead(BaseModel):
transaction: TransactionRead = Field()
account_split: SplitRead = Field()
balance: decimal.Decimal = Field()

View File

@@ -5,15 +5,14 @@ from fastapi.security import OAuth2PasswordBearer
from fastapi.middleware.cors import CORSMiddleware
from fastapi_pagination import add_pagination
from db import create_db_and_tables
from user import user_router, auth_router, create_admin_user
from account.account_routes import router as account_router
from account.category_routes import router as category_router
from ledger.routes import router as ledger_router
from payee.routes import router as payee_router
from transaction.routes import router as transaction_router
@asynccontextmanager
async def lifespan(app: FastAPI):
create_db_and_tables()
@@ -41,6 +40,7 @@ app.include_router(auth_router, prefix="/auth", tags=["auth"], )
app.include_router(user_router, prefix="/users", tags=["users"])
app.include_router(account_router, prefix="/accounts", tags=["accounts"])
app.include_router(category_router, prefix="/categories", tags=["categories"])
app.include_router(ledger_router, prefix="/ledgers", tags=["ledgers"])
app.include_router(payee_router, prefix="/payees", tags=["payees"])
app.include_router(transaction_router, prefix="/transactions", tags=["transactions"])

94
api/app/payee/fixtures.py Normal file
View File

@@ -0,0 +1,94 @@
from datetime import date
from decimal import Decimal
from account.resource import AccountResource
from account.schemas import AccountCreate, CategoryCreate
from payee.models import PayeeCreate, Payee
def inject_fixtures(session):
for f in fixtures_payee:
# f = prepare_dict(session, f)
schema = PayeeCreate(**f)
Payee.create(schema, session)
def prepare_dict(session, entry):
if entry['parent_path']:
parent = AccountResource.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_payee = [
{
"name": "PayeeEmployer",
},
{
"name": "PayeeSocialSecurity",
},
{
"name": "PayeeAssurance1",
},
{
"name": "PayeeAssurance2",
},
{
"name": "PayeeSupermarket1",
},
{
"name": "PayeeSupermarket2",
},
{
"name": "PayeeTaxes",
},
{
"name": "PayeeRent",
},
]
fixtures_account = [
{
"name": "Current Assets",
"parent_path": None,
"type": "Asset",
"opening_date": date(1970, 1, 2),
"opening_balance": Decimal("0.00"),
},
{
"name": "Cash in Wallet",
"parent_path": "/Accounts/Asset/Current Assets/",
"type": "Asset",
"opening_date": date(1970, 1, 3),
"opening_balance": Decimal("0.00"),
},
{
"name": "Checking Account",
"parent_path": "/Accounts/Asset/Current Assets/",
"type": "Asset",
"opening_date": date(1970, 1, 4),
"opening_balance": Decimal("0.00"),
},
{
"name": "Savings Account",
"parent_path": "/Accounts/Asset/Current Assets/",
"type": "Asset",
"opening_date": date(1970, 1, 5),
"opening_balance": Decimal("0.00"),
},
{
"name": "Debt Accounts",
"parent_path": None,
"type": "Liability",
"opening_date": date(1970, 1, 6),
"opening_balance": Decimal("0.00"),
},
{
"name": "Credit Card",
"parent_path": "/Accounts/Liability/Debt Accounts/",
"type": "Liability",
"opening_date": date(1970, 1, 7),
"opening_balance": Decimal("0.00"),
},
]

View File

@@ -0,0 +1,36 @@
import random
from datetime import date, timedelta
from account.resource import AccountResource
from transaction.models import TransactionCreate, Split
from transaction.resource import TransactionResource
def inject_fixtures(session):
for f in fixtures_transaction:
for i in range(f["count"]):
data = prepare_dict(session, f, i)
schema = TransactionCreate(**data)
schema.splits.append(Split(**data))
TransactionResource.create(session, schema)
def prepare_dict(session, entry, iteration):
account = AccountResource.get_by_path(session, entry['account_path'])
result = entry.copy()
result["account_id"] = account.id
result["payee_id"] = None
result["splits"] = []
result["transaction_date"] = entry["start_date"] + timedelta(days=(iteration / 2))
result["amount"] = [10, 100, 1000][random.Random().randint(0,2)] * [1, -1][random.Random().randint(0,1)]
result["id"] = 0
return result
fixtures_transaction = [
{
"account_path": "/Accounts/Asset/Current Assets/Checking Account/",
"start_date": date(1970, 1, 5),
"count": 200
},
]

View File

@@ -1,8 +1,9 @@
from datetime import date
from decimal import Decimal
from typing import Optional
from uuid import UUID, uuid4
from sqlmodel import Field, SQLModel, select, Relationship
from sqlmodel import Field, SQLModel, Relationship
from pydantic import Field as PydField
from account.models import Account
@@ -11,43 +12,15 @@ from payee.models import Payee, PayeeRead
class TransactionBase(SQLModel):
pass
transaction_date: date = Field()
payment_date: Optional[date] = Field(default=None)
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(cls.model_validate(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()
sequence: Optional[int] = Field(default=None)
class SplitBase(SQLModel):
account_id: UUID = Field(foreign_key="account.id")

View File

@@ -1,20 +1,21 @@
from decimal import Decimal
from typing import Optional
from uuid import UUID, uuid4
from sqlalchemy.orm import aliased
from sqlmodel import Field, SQLModel, select, Relationship
from pydantic import Field as PydField
from sqlmodel import select, func
from account.models import Account
from account.schemas import AccountRead
from transaction.models import Transaction, Split
from payee.models import Payee, PayeeRead
class TransactionResource:
@classmethod
def create(cls, transaction, session):
def get_sequence_number(cls, session):
stmt = select(func.max(Transaction.sequence)).select_from(Transaction)
sequence = session.execute(stmt).first()
return sequence[0] + 1 if sequence[0] else 1
@classmethod
def create(cls, session, transaction):
transaction_db = Transaction.model_validate(transaction)
transaction_db.sequence = cls.get_sequence_number(session)
session.add(transaction_db)
session.commit()
session.refresh(transaction_db)
@@ -29,16 +30,6 @@ class TransactionResource:
def get(cls, session, transaction_id):
return session.get(Transaction, transaction_id)
@classmethod
def get_opening_transaction(cls, session, account_id):
split_account = aliased(Split)
split_equity = aliased(Split)
account_filter = aliased(Account)
return session.exec(select(Transaction)
.join(split_account, split_account.account_id == account_id)
.join(split_equity)
.join(account_filter, account_filter.id == split_equity.account_id and Account.path == "/Equity/")).first()
@classmethod
def update(cls, session, transaction_db, transaction_data):
transaction_db.sqlmodel_update(Transaction.model_validate(transaction_data))

View File

@@ -4,43 +4,44 @@ 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 transaction.models import TransactionCreate, TransactionRead, TransactionUpdate
from transaction.resource import TransactionResource
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)
result = TransactionResource.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())
return paginate(session, TransactionResource.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)
transaction = TransactionResource.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)
db_transaction = TransactionResource.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)
transaction = TransactionResource.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)
transaction = TransactionResource.get(session, transaction_id)
if not transaction:
raise HTTPException(status_code=404, detail="Transaction not found")
Transaction.delete(session, transaction)
TransactionResource.delete(session, transaction)
return {"ok": True}

View File

@@ -28,6 +28,7 @@ import { AccountList, AccountCreate, AccountEdit } from "./pages/accounts";
import { CategoryList, CategoryCreate, CategoryEdit } from "./pages/categories";
import { PayeeCreate, PayeeEdit, PayeeList } from "./pages/payees";
import { TransactionCreate, TransactionEdit, TransactionList } from "./pages/transactions";
import {AccountLedger} from "./pages/accounts/ledger";
/**
* mock auth credentials to simulate authentication
@@ -135,6 +136,7 @@ const App: React.FC = () => {
<Route index element={<AccountList />} />
<Route path="create" element={<AccountCreate />} />
<Route path="edit/:id" element={<AccountEdit />} />
<Route path="ledger/:id" element={<AccountLedger />} />
</Route>
<Route path="/categories">
<Route index element={<CategoryList />} />

View File

@@ -13,9 +13,9 @@ export const AccountEdit: React.FC = () => {
id={id}
/>
<CrudForm
schemaName={"AccountUpdate"}
resource={`accounts/${id}/opening`}
id={id}
schemaName={"OpeningTransactionUpdate"}
resource={`accounts/${id}/opening_state`}
id=""
/>
</div>
);

View File

@@ -0,0 +1,69 @@
import React from "react";
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import { useParams } from "react-router";
import {useList, useOne} from "@refinedev/core";
export const AccountLedger: React.FC = () => {
const { id } = useParams()
const { data, isLoading, isError } = useList({
resource: `ledgers/${id}`,
});
if (isLoading) {
return <div>Loading</div>
}
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell></TableCell>
<TableCell align="right">Date</TableCell>
<TableCell align="right">Deposit</TableCell>
<TableCell align="right">Payment</TableCell>
<TableCell align="right">Balance</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data?.data.map((row: any) => {
const is_expense: boolean = row.account_split.amount < 0
const destination_accounts = []
for (const split of row.transaction.splits) {
if ((is_expense && split.amount >= 0) || (!is_expense && split.amount < 0)) {
destination_accounts.push(split)
}
}
let is_split = false;
if (destination_accounts) {
if (destination_accounts.length > 1) {
is_split = true;
}
}
return(
<TableRow
key={row.name}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row"></TableCell>
<TableCell align="right">{row.transaction.transaction_date}</TableCell>
<TableCell align="right">{ is_expense ? "" : row.account_split.amount }</TableCell>
<TableCell align="right">{ is_expense ? row.account_split.amount : "" }</TableCell>
<TableCell align="right">{ row.balance }</TableCell>
</TableRow>)
})}
</TableBody>
</Table>
</TableContainer>
);
}

View File

@@ -1,7 +1,8 @@
import React from "react";
import { ButtonGroup } from "@mui/material";
import { Button, ButtonGroup } from "@mui/material";
import { DataGrid, type GridColDef } from "@mui/x-data-grid";
import RequestQuoteIcon from '@mui/icons-material/RequestQuote';
import { DeleteButton, EditButton, List, useDataGrid} from "@refinedev/mui";
@@ -15,7 +16,7 @@ export const AccountList: React.FC = () => {
{ field: "id", headerName: "ID" },
{ field: "name", headerName: "Name", flex: 1 },
{ field: "path", headerName: "path", flex: 1 },
{ field: "type", headerName: "Type", flex: 0.3 },
{ field: "type", headerName: "Type", flex: 1 },
{
field: "actions",
headerName: "Actions",
@@ -23,6 +24,7 @@ export const AccountList: React.FC = () => {
renderCell: function render({ row }) {
return (
<ButtonGroup>
<Button href={`/accounts/ledger/${row.id}`}><RequestQuoteIcon /></Button>
<EditButton hideText recordItemId={row.id} />
<DeleteButton hideText recordItemId={row.id} />
</ButtonGroup>
@@ -30,6 +32,7 @@ export const AccountList: React.FC = () => {
},
align: "center",
headerAlign: "center",
flex: 1,
},
],
[],

View File

@@ -67,11 +67,7 @@ export const authProvider: AuthProvider = {
onError: async (error) => {
if (error?.status === 401) {
localStorage.removeItem("access_token");
return Promise<{
redirectTo: "/login",
logout: true,
error: { message: "Unauthorized" },
}>;
return {redirectTo: "/login"}
}
return {};
},

View File

@@ -14,8 +14,7 @@ const fetcher = async (url: string, options?: RequestInit) => {
export const dataProvider: DataProvider = {
getOne: async ({ resource, id, meta }) => {
const response = await fetcher(`${API_URL}/${resource}/${id}`);
const response = id !== "" ? await fetcher(`${API_URL}/${resource}/${id}`) : await fetcher(`${API_URL}/${resource}`);
if (response.status < 200 || response.status > 299) throw response;
const data = await response.json();