Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: update expense options #37

Merged
merged 6 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/routers/category.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
from typing import List

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session

import services
from database import get_db
from dependencies import get_current_user
from models import User
from schemas import CategoryModel, CategoryCreate, IconColor, CategoriesGroup
from schemas import (
CategoryModel,
CategoryCreate,
IconColor,
CategoriesGroup,
CategoriesGroupDetail,
)

router = APIRouter(
prefix="/groups",
tags=["categories"],
)


@router.get("/categories/", response_model=List[CategoriesGroupDetail])
def read_categories_group_detail(
*,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> List[CategoriesGroupDetail]:
return services.read_categories_group_detail(db, current_user.id)


@router.get("/{group_id}/categories/", response_model=CategoriesGroup)
def read_categories_group(
*,
Expand Down
4 changes: 2 additions & 2 deletions src/routers/expense.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
Page,
)
from models import User
from schemas import ExpenseCreate, ExpenseModel, UserExpense
from schemas import ExpenseCreate, ExpenseModel, UserExpense, ExpenseUpdate

router = APIRouter(
prefix="/groups",
Expand All @@ -41,7 +41,7 @@ def update_expense(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
group_id: int,
expense: ExpenseCreate,
expense: ExpenseUpdate,
expense_id: int,
) -> ExpenseModel:
return services.update_expense(db, current_user.id, group_id, expense, expense_id)
Expand Down
3 changes: 2 additions & 1 deletion src/schemas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@
UserSpender,
GroupDailyExpenses,
GroupDailyExpensesDetail,
CategoriesGroupDetail,
)
from .invintation import BaseInvitation, InvitationCreate, InvitationModel
from .category import CategoryModel, CategoryCreate, IconColor
from .expense import ExpenseCreate, ExpenseModel, UserExpense
from .expense import ExpenseCreate, ExpenseUpdate, ExpenseModel, UserExpense
from .replenishment import (
ReplenishmentCreate,
UserBalance,
Expand Down
5 changes: 5 additions & 0 deletions src/schemas/expense.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ class ExpenseCreate(BaseModel):
category_id: int


class ExpenseUpdate(ExpenseCreate):
group_id: int
time: datetime.datetime


class CategoryGroup(BaseModel):
group: ShortGroup
category: CategoryModel
Expand Down
8 changes: 8 additions & 0 deletions src/schemas/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ class CategoriesGroup(BaseModel):
categories_group: List[AboutCategory]


class CategoriesGroupDetail(BaseModel):
id: int
title: str
icon_url: str
color_code: str
categories_group: List[AboutCategory]


class GroupInfo(GroupModel):
members: int
expenses: int
Expand Down
1 change: 1 addition & 0 deletions src/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
read_group_member_daily_expenses,
read_group_member_daily_expenses_detail,
read_group_member_history,
read_categories_group_detail,
)
from .invitation import create_invitation, read_invitations, response_invitation
from .replenishment import (
Expand Down
124 changes: 80 additions & 44 deletions src/services/expense.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime
from typing import List, Optional
from typing import List, Optional, Union

from pydantic.schema import date
from sqlalchemy import and_, exc, extract
Expand All @@ -10,59 +10,100 @@

from models import CategoryGroup, Expense, UserGroup
from enums import GroupStatusEnum
from schemas import ExpenseCreate, ExpenseModel, UserExpense
from schemas import ExpenseCreate, ExpenseModel, UserExpense, ExpenseUpdate


def validate_input_data(
def validate_user_group(db: Session, user_id: int, group_id: int) -> UserGroup:
try:
db_user_group = (
db.query(UserGroup).filter_by(group_id=group_id, user_id=user_id).one()
)
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="You are not a user of this group!",
)
return db_user_group


def validate_expense(
db: Session,
user_id: int,
group_id: int,
expense_id: int,
) -> None:
try:
db.query(Expense).filter_by(id=expense_id, user_id=user_id).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="It's not your expense!",
)
try:
db.query(Expense).filter_by(
id=expense_id, user_id=user_id, group_id=group_id
).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="The expense does not belong to this group!",
)


def validate_expense_update(
db: Session,
user_id: int,
group_id: int,
expense: ExpenseCreate = None,
expense_id: int = None,
is_create: bool = False,
expense: ExpenseUpdate,
) -> None:
try:
db_user_group = (
db.query(UserGroup).filter_by(group_id=group_id, user_id=user_id).one()
db.query(UserGroup)
.filter_by(group_id=expense.group_id, user_id=user_id)
.one()
)
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="You are not a user of this group!",
detail="You are not a user of the group specified to update expenses!",
)
if is_create:
if db_user_group.status == GroupStatusEnum.INACTIVE:
if db_user_group.status == GroupStatusEnum.INACTIVE:
if group_id != expense.group_id:
raise HTTPException(
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
detail="The user is not active in this group!",
)
if expense:
try:
db.query(CategoryGroup).filter_by(
category_id=expense.category_id,
group_id=group_id,
).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="The group does not have such a category!",
)
if expense_id:
try:
db.query(Expense).filter_by(id=expense_id, user_id=user_id).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="It's not your expense!",
detail="The user is not active in group specified to update expenses!",
)
try:
db.query(CategoryGroup).filter_by(
category_id=expense.category_id,
group_id=expense.group_id,
).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="The group specified to update expenses does not contain this category!",
)


def create_expense(
db: Session, user_id: int, group_id: int, expense: ExpenseCreate
) -> ExpenseModel:
validate_input_data(
db=db, user_id=user_id, group_id=group_id, expense=expense, is_create=True
)
db_user_group = validate_user_group(db=db, user_id=user_id, group_id=group_id)
if db_user_group.status == GroupStatusEnum.INACTIVE:
raise HTTPException(
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
detail="The user is not active in this group!",
)
try:
db.query(CategoryGroup).filter_by(
category_id=expense.category_id,
group_id=group_id,
).one()
except exc.NoResultFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="The group does not have such a category!",
)
db_expense = Expense(**expense.dict())
db_expense.user_id = user_id
db_expense.group_id = group_id
Expand All @@ -80,15 +121,11 @@ def create_expense(


def update_expense(
db: Session, user_id: int, group_id: int, expense: ExpenseCreate, expense_id: int
db: Session, user_id: int, group_id: int, expense: ExpenseUpdate, expense_id: int
) -> ExpenseModel:
validate_input_data(
db=db,
user_id=user_id,
group_id=group_id,
expense=expense,
expense_id=expense_id,
)
validate_user_group(db=db, user_id=user_id, group_id=group_id)
validate_expense(db=db, user_id=user_id, group_id=group_id, expense_id=expense_id)
validate_expense_update(db=db, user_id=user_id, group_id=group_id, expense=expense)
db.query(Expense).filter_by(id=expense_id).update(values={**expense.dict()})
db_expense = db.query(Expense).filter_by(id=expense_id).one()
try:
Expand All @@ -103,9 +140,8 @@ def update_expense(


def delete_expense(db: Session, user_id: int, group_id: int, expense_id: int) -> None:
validate_input_data(
db=db, user_id=user_id, group_id=group_id, expense_id=expense_id
)
validate_user_group(db=db, user_id=user_id, group_id=group_id)
validate_expense(db=db, user_id=user_id, group_id=group_id, expense_id=expense_id)
db.query(Expense).filter_by(id=expense_id).delete()
try:
db.commit()
Expand Down
20 changes: 20 additions & 0 deletions src/services/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
GroupMember,
UserDailyExpenses,
UserDailyExpensesDetail,
CategoriesGroupDetail,
)
from enums import GroupStatusEnum

Expand Down Expand Up @@ -363,6 +364,25 @@ def read_user_groups(db: Session, user_id: int) -> UserGroups:
return db_query


def read_categories_group_detail(
db: Session, user_id: int
) -> List[CategoriesGroupDetail]:
db_query = (
db.query(Group)
.options(joinedload(Group.categories_group))
.join(UserGroup)
.filter(
and_(
UserGroup.group_id == Group.id,
UserGroup.user_id == user_id,
UserGroup.status == GroupStatusEnum.ACTIVE,
)
)
.all()
)
return db_query


def read_categories_group(db: Session, user_id: int, group_id: int) -> CategoriesGroup:
try:
db.query(UserGroup).filter_by(
Expand Down
52 changes: 48 additions & 4 deletions tests/test_endpoints/test_expense_e.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ def test_update_expense(self) -> None:
json={
"descriptions": date_update_expense.descriptions,
"amount": date_update_expense.amount,
"category_id": date_update_expense.category_id,
"category_id": self.category.id,
"group_id": self.second_group.id,
"time": "2018-08-03T10:51:42.990",
},
)
expense_data = {
Expand All @@ -107,9 +109,9 @@ def test_update_expense(self) -> None:
"time": data.json()["time"],
"category_group": {
"group": {
"id": self.first_group.id,
"title": self.first_group.title,
"color_code": self.first_group.color_code,
"id": self.second_group.id,
"title": self.second_group.title,
"color_code": self.second_group.color_code,
},
"category": {"title": self.category.title, "id": self.category.id},
"icon_url": self.icon_url,
Expand All @@ -120,6 +122,48 @@ def test_update_expense(self) -> None:
assert data.status_code == 200
assert data.json() == expense_data

def test_update_expense_on_other_group(self) -> None:
expense = ExpenseFactory(
user_id=self.user.id,
group_id=self.first_group.id,
category_id=self.category.id,
)
date_update_expense = ExpenseCreate(
descriptions="descriptions", amount=999.9, category_id=self.category.id
)
data = client.put(
f"/groups/{self.first_group.id}/expenses/{expense.id}/",
json={
"descriptions": date_update_expense.descriptions,
"amount": date_update_expense.amount,
"category_id": date_update_expense.category_id,
"group_id": 99999,
"time": "2018-08-03T10:51:42.990",
},
)
assert data.status_code == 404

def test_update_expense_on_other_group_category(self) -> None:
expense = ExpenseFactory(
user_id=self.user.id,
group_id=self.first_group.id,
category_id=self.category.id,
)
date_update_expense = ExpenseCreate(
descriptions="descriptions", amount=999.9, category_id=self.category.id
)
data = client.put(
f"/groups/{self.first_group.id}/expenses/{expense.id}/",
json={
"descriptions": date_update_expense.descriptions,
"amount": date_update_expense.amount,
"category_id": 9999,
"group_id": self.first_group.id,
"time": "2018-08-03T10:51:42.990",
},
)
assert data.status_code == 404

def test_delete_expense(self) -> None:
expense = ExpenseFactory(
user_id=self.user.id,
Expand Down
Loading
Loading