From f9ead58be15e671bc9a67e0c111105add76b8781 Mon Sep 17 00:00:00 2001 From: gabokatta Date: Sun, 16 Jun 2024 13:53:13 -0300 Subject: [PATCH 1/3] Add: payment confirmation and balance update --- src/crud.py | 92 ++++++++++++++++++++++++++++++++++++++++-------- src/main.py | 51 ++++++++++++++++++++++----- src/models.py | 3 ++ src/schemas.py | 4 +-- src/test_main.py | 64 ++++++++++++++++++++++++++++----- 5 files changed, 182 insertions(+), 32 deletions(-) diff --git a/src/crud.py b/src/crud.py index a863df4..19d21c7 100755 --- a/src/crud.py +++ b/src/crud.py @@ -137,23 +137,58 @@ def add_user_to_group(db: Session, user: models.User, group: models.Group): # ALL SPENDINGS ################################################ + def get_all_spendings_by_group_id(db: Session, group_id: int): - - unique_spendings = db.query(models.UniqueSpending).filter(models.UniqueSpending.group_id == group_id).limit(100).all() - installment_spendings = db.query(models.InstallmentSpending).filter(models.InstallmentSpending.group_id == group_id).limit(100).all() - recurring_spendings = db.query(models.RecurringSpending).filter(models.RecurringSpending.group_id == group_id).limit(100).all() - unique_spendings = list(map(lambda spending: {**spending.__dict__, "type": "unique_spending"}, unique_spendings)) - installment_spendings = list(map(lambda spending: {**spending.__dict__, "type": "installment_spending"}, installment_spendings)) - recurring_spendings = list(map(lambda spending: {**spending.__dict__, "type": "recurring_spending"}, recurring_spendings)) + unique_spendings = ( + db.query(models.UniqueSpending) + .filter(models.UniqueSpending.group_id == group_id) + .limit(100) + .all() + ) + installment_spendings = ( + db.query(models.InstallmentSpending) + .filter(models.InstallmentSpending.group_id == group_id) + .limit(100) + .all() + ) + recurring_spendings = ( + db.query(models.RecurringSpending) + .filter(models.RecurringSpending.group_id == group_id) + .limit(100) + .all() + ) + + unique_spendings = list( + map( + lambda spending: {**spending.__dict__, "type": "unique_spending"}, + unique_spendings, + ) + ) + installment_spendings = list( + map( + lambda spending: {**spending.__dict__, "type": "installment_spending"}, + installment_spendings, + ) + ) + recurring_spendings = list( + map( + lambda spending: {**spending.__dict__, "type": "recurring_spending"}, + recurring_spendings, + ) + ) return unique_spendings + installment_spendings + recurring_spendings + ################################################ # UNIQUE SPENDINGS ################################################ -def create_unique_spending(db: Session, spending: schemas.UniqueSpendingCreate, user_id: int): + +def create_unique_spending( + db: Session, spending: schemas.UniqueSpendingCreate, user_id: int +): db_spending = models.UniqueSpending(owner_id=user_id, **dict(spending)) db.add(db_spending) db.commit() @@ -177,8 +212,15 @@ def get_unique_spendings_by_group_id(db: Session, group_id: int): ################################################ -def create_installment_spending(db: Session, spending: schemas.InstallmentSpendingCreate, user_id: int, current_installment:int): - db_spending = models.InstallmentSpending(owner_id=user_id, current_installment=current_installment,**dict(spending)) +def create_installment_spending( + db: Session, + spending: schemas.InstallmentSpendingCreate, + user_id: int, + current_installment: int, +): + db_spending = models.InstallmentSpending( + owner_id=user_id, current_installment=current_installment, **dict(spending) + ) db.add(db_spending) db.commit() db.refresh(db_spending) @@ -201,7 +243,9 @@ def get_installment_spendings_by_group_id(db: Session, group_id: int): ################################################ -def create_recurring_spending(db: Session, spending: schemas.RecurringSpendingBase, user_id: int): +def create_recurring_spending( + db: Session, spending: schemas.RecurringSpendingBase, user_id: int +): db_spending = models.RecurringSpending(owner_id=user_id, **dict(spending)) db.add(db_spending) db.commit() @@ -212,7 +256,11 @@ def create_recurring_spending(db: Session, spending: schemas.RecurringSpendingBa def get_recurring_spendings_by_id(db: Session, recurring_spendig_id: int): - return db.query(models.RecurringSpending).filter(models.RecurringSpending.id == recurring_spendig_id).first() + return ( + db.query(models.RecurringSpending) + .filter(models.RecurringSpending.id == recurring_spendig_id) + .first() + ) def get_recurring_spendings_by_group_id(db: Session, group_id: int): @@ -224,7 +272,11 @@ def get_recurring_spendings_by_group_id(db: Session, group_id: int): ) -def put_recurring_spendings(db: Session, db_recurring_spending: models.RecurringSpending, put_recurring_spending: schemas.RecurringSpendingPut): +def put_recurring_spendings( + db: Session, + db_recurring_spending: models.RecurringSpending, + put_recurring_spending: schemas.RecurringSpendingPut, +): db_recurring_spending.amount = put_recurring_spending.amount db_recurring_spending.description = put_recurring_spending.description db_recurring_spending.category_id = put_recurring_spending.categiry_id @@ -240,13 +292,16 @@ def put_recurring_spendings(db: Session, db_recurring_spending: models.Recurring def create_payment(db: Session, payment: schemas.PaymentCreate): db_payment = models.Payment(**dict(payment)) - update_balances_from_payment(db, db_payment) db.add(db_payment) db.commit() db.refresh(db_payment) return db_payment +def get_payment_by_id(db: Session, payment_id: int): + return db.query(models.Payment).filter(models.Payment.id == payment_id).first() + + def get_payments_by_group_id(db: Session, group_id: int): return ( db.query(models.Payment) @@ -255,6 +310,15 @@ def get_payments_by_group_id(db: Session, group_id: int): .all() ) + +def confirm_payment(db: Session, db_payment: models.Payment): + db_payment.confirmed = True + update_balances_from_payment(db, db_payment) + db.commit() + db.refresh(db_payment) + return db_payment + + ################################################ # BUDGETS ################################################ diff --git a/src/main.py b/src/main.py index bb27bef..07650c4 100755 --- a/src/main.py +++ b/src/main.py @@ -293,10 +293,12 @@ def list_group_categories(db: DbDependency, user: UserDependency, group_id: int) categories = crud.get_categories_by_group_id(db, group_id) return categories + ################################################ # ALL SPENDINGS ################################################ + @app.get("/group/{group_id}/spending") def list_group_unique_spendings(db: DbDependency, user: UserDependency, group_id: int): group = crud.get_group_by_id(db, group_id) @@ -364,15 +366,19 @@ def create_installment_spending( amount_of_installments = spending.amount_of_installments spending_date = spending.date for i in range(amount_of_installments): - spending.description = f"{spending_description} | cuota {i+1}/{amount_of_installments}" - spending.date = spending_date + timedelta(days=(30*i)) - res.append(crud.create_installment_spending(db, spending, user.id, i+1)) + spending.description = ( + f"{spending_description} | cuota {i+1}/{amount_of_installments}" + ) + spending.date = spending_date + timedelta(days=(30 * i)) + res.append(crud.create_installment_spending(db, spending, user.id, i + 1)) return res @app.get("/group/{group_id}/installment-spending") -def list_group_installment_spendings(db: DbDependency, user: UserDependency, group_id: int): +def list_group_installment_spendings( + db: DbDependency, user: UserDependency, group_id: int +): group = crud.get_group_by_id(db, group_id) check_group_exists_and_user_is_member(user.id, group) @@ -380,7 +386,6 @@ def list_group_installment_spendings(db: DbDependency, user: UserDependency, gro return crud.get_installment_spendings_by_group_id(db, group_id) - ################################################ # RECURRING SPENDINGS ################################################ @@ -405,13 +410,16 @@ def create_recurring_spending( @app.get("/group/{group_id}/recurring-spending") -def list_group_recurring_spendings(db: DbDependency, user: UserDependency, group_id: int): +def list_group_recurring_spendings( + db: DbDependency, user: UserDependency, group_id: int +): group = crud.get_group_by_id(db, group_id) check_group_exists_and_user_is_member(user.id, group) return crud.get_recurring_spendings_by_group_id(db, group_id) + @app.put("/recurring-spending/{recurring_spending_id}") def put_recurring_spendings( db: DbDependency, @@ -420,7 +428,9 @@ def put_recurring_spendings( put_recurring_spending: schemas.RecurringSpendingPut, ): - db_recurring_spending = crud.get_recurring_spendings_by_id(db, recurring_spending_id) + db_recurring_spending = crud.get_recurring_spendings_by_id( + db, recurring_spending_id + ) if db_recurring_spending is None: raise HTTPException( @@ -432,7 +442,9 @@ def put_recurring_spendings( check_group_exists_and_user_is_member(user.id, group) check_group_is_unarchived(group) - return crud.put_recurring_spendings(db, db_recurring_spending, put_recurring_spending) + return crud.put_recurring_spendings( + db, db_recurring_spending, put_recurring_spending + ) ################################################ @@ -466,6 +478,29 @@ def create_payment( return crud.create_payment(db, payment) +@app.post("/payment/{payment_id}/confirm", status_code=HTTPStatus.OK) +def confirm_payment(db: DbDependency, user: UserDependency, payment_id: int): + + payment = crud.get_payment_by_id(db, payment_id) + if payment is None: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail=f"No se consiguiĆ³ el pago con ID: {payment_id}.", + ) + + if payment.to_id != user.id: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=f"Solo el receptor del pago puede confirmarlo.", + ) + + confirmed_payment = crud.confirm_payment(db, payment) + + # TODO Actualizar Balances + + return confirmed_payment + + @app.get("/payment") def list_payments(db: DbDependency, user: UserDependency, group_id: int): group = crud.get_group_by_id(db, group_id) diff --git a/src/models.py b/src/models.py index 5e20821..076fbc1 100755 --- a/src/models.py +++ b/src/models.py @@ -54,6 +54,7 @@ class Category(Base): __table_args__ = (UniqueConstraint("group_id", "name"),) + class UniqueSpending(Base): __tablename__ = "unique_spendings" @@ -79,6 +80,7 @@ class InstallmentSpending(Base): current_installment = Column(Integer) date: Mapped[datetime] = mapped_column(DateTime, default=func.now()) + class RecurringSpending(Base): __tablename__ = "recurring_spendings" @@ -99,6 +101,7 @@ class Payment(Base): from_id = Column(ForeignKey("users.id")) to_id = Column(ForeignKey("users.id")) amount = Column(Integer) + confirmed = Column(Boolean, default=False) date: Mapped[datetime] = mapped_column(DateTime, default=func.now()) diff --git a/src/schemas.py b/src/schemas.py index 0882950..62d069a 100755 --- a/src/schemas.py +++ b/src/schemas.py @@ -154,6 +154,7 @@ class RecurringSpending(RecurringSpendingBase): id: int owner_id: int + ################################################ # PAYMENTS ################################################ @@ -175,8 +176,6 @@ class Payment(PaymentBase): id: int - - ################################################ # BUDGETS ################################################ @@ -213,6 +212,7 @@ class InviteStatus(StrEnum): ACCEPTED = auto() EXPIRED = auto() + class InviteBase(BaseModel): creation_date: Optional[datetime] = Field(None) receiver_id: Optional[int] = Field(None) diff --git a/src/test_main.py b/src/test_main.py index 7a5feb8..fe95ece 100755 --- a/src/test_main.py +++ b/src/test_main.py @@ -953,14 +953,9 @@ def some_payment( some_other_credentials: schemas.UserCredentials, some_group: schemas.Group, ): - res = client.post( - url=f"/group/{some_group.id}/member", - json={ - "user_identifier": some_other_credentials.id, - }, - headers={"x-user": some_credentials.jwt}, + add_user_to_group( + client, some_group.id, some_other_credentials.id, some_credentials ) - assert res.status_code == HTTPStatus.CREATED response = client.post( url="/payment", @@ -980,19 +975,72 @@ def some_payment( return schemas.Payment(**response_body) +@pytest.fixture +def some_payment_confirmation( + client: TestClient, + some_other_credentials: schemas.UserCredentials, + some_payment: schemas.Payment, +): + response = client.post( + url=f"/payment/{some_payment.id}/confirm", + headers={"x-user": some_other_credentials.jwt}, + ) + response_body = response.json() + assert response.status_code == HTTPStatus.OK + assert response_body["confirmed"] == True + return schemas.Payment(**response_body) + + def test_create_payment(some_payment: schemas.Payment): # NOTE: test is inside fixture pass +def test_create_payment_confirmation(some_payment_confirmation: schemas.Payment): + # NOTE: test is inside fixture + pass + + +def test_confirm_non_existant_payment( + client: TestClient, + some_credentials: schemas.UserCredentials, + some_other_credentials: schemas.UserCredentials, + some_group: schemas.Group, +): + + add_user_to_group( + client, some_group.id, some_other_credentials.id, some_credentials + ) + + response = client.post( + url=f"/payment/{123}/confirm", + headers={"x-user": some_other_credentials.jwt}, + ) + assert response.status_code == HTTPStatus.NOT_FOUND + + +def test_confirm_non_existant_payment( + client: TestClient, + some_credentials: schemas.UserCredentials, + some_payment: schemas.Payment, +): + + response = client.post( + url=f"/payment/{some_payment.id}/confirm", + headers={"x-user": some_credentials.jwt}, + ) + assert response.status_code == HTTPStatus.BAD_REQUEST + + def test_payment_updates_balance( client: TestClient, some_credentials: schemas.UserCredentials, some_other_credentials: schemas.UserCredentials, some_payment: schemas.Payment, + some_payment_confirmation: schemas.Payment, ): response = client.get( - url=f"/group/{some_payment.group_id}/balance", + url=f"/group/{some_payment_confirmation.group_id}/balance", headers={"x-user": some_credentials.jwt}, ) assert response.status_code == HTTPStatus.OK From 8f14ec1a7ba829b2f3525e14df41891b1fdf4238 Mon Sep 17 00:00:00 2001 From: gabokatta Date: Sun, 16 Jun 2024 13:54:00 -0300 Subject: [PATCH 2/3] Removed: TODOs --- src/main.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main.py b/src/main.py index 07650c4..875a91f 100755 --- a/src/main.py +++ b/src/main.py @@ -493,12 +493,8 @@ def confirm_payment(db: DbDependency, user: UserDependency, payment_id: int): status_code=HTTPStatus.BAD_REQUEST, detail=f"Solo el receptor del pago puede confirmarlo.", ) - - confirmed_payment = crud.confirm_payment(db, payment) - - # TODO Actualizar Balances - - return confirmed_payment + + return crud.confirm_payment(db, payment) @app.get("/payment") From 4a00d04e5fd0c78c6fc0f0f6d371d850a30e2ec0 Mon Sep 17 00:00:00 2001 From: gabokatta Date: Sun, 16 Jun 2024 15:27:53 -0300 Subject: [PATCH 3/3] Add: already confirmed validation. --- src/main.py | 10 ++++++++-- src/test_main.py | 13 +++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main.py b/src/main.py index 875a91f..b388601 100755 --- a/src/main.py +++ b/src/main.py @@ -485,7 +485,13 @@ def confirm_payment(db: DbDependency, user: UserDependency, payment_id: int): if payment is None: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, - detail=f"No se consiguiĆ³ el pago con ID: {payment_id}.", + detail=f"No se consiguiĆ³ el pago.", + ) + + if payment.confirmed: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=f"Este pago ya fue confirmado.", ) if payment.to_id != user.id: @@ -493,7 +499,7 @@ def confirm_payment(db: DbDependency, user: UserDependency, payment_id: int): status_code=HTTPStatus.BAD_REQUEST, detail=f"Solo el receptor del pago puede confirmarlo.", ) - + return crud.confirm_payment(db, payment) diff --git a/src/test_main.py b/src/test_main.py index fe95ece..15d40a2 100755 --- a/src/test_main.py +++ b/src/test_main.py @@ -1032,6 +1032,19 @@ def test_confirm_non_existant_payment( assert response.status_code == HTTPStatus.BAD_REQUEST +def test_confirm_already_confirmed( + client: TestClient, + some_other_credentials: schemas.UserCredentials, + some_payment_confirmation: schemas.Payment, +): + + response = client.post( + url=f"/payment/{some_payment_confirmation.id}/confirm", + headers={"x-user": some_other_credentials.jwt}, + ) + assert response.status_code == HTTPStatus.BAD_REQUEST + + def test_payment_updates_balance( client: TestClient, some_credentials: schemas.UserCredentials,