Skip to content

Commit

Permalink
Merge pull request #57 from VaquitApp/feature/Registrar-Pago-Personal
Browse files Browse the repository at this point in the history
Registrar Pago Personal [5]
  • Loading branch information
ovr4ulin authored Jun 12, 2024
2 parents 5044d0b + 9bacd8f commit 3f5deab
Show file tree
Hide file tree
Showing 9 changed files with 656 additions and 364 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@ pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python

*.db
.vscode
667 changes: 370 additions & 297 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fastapi = "^0.111.0"
sqlalchemy = "^2.0.30"
uvicorn = "^0.29.0"
flake8 = "^7.0.0"
psycopg2 = "^2.9.9"
psycopg2-binary = "^2.9.9"
python-jose = {extras = ["cryptography"], version = "^3.3.0"}
sib-api-v3-sdk = "^7.6.0"

Expand Down
74 changes: 53 additions & 21 deletions src/crud.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List
from typing import List, Optional
from sqlalchemy import select
from sqlalchemy.orm import Session
from uuid import UUID
Expand All @@ -15,7 +15,7 @@ def get_user_by_id(db: Session, id: int):
return db.query(models.User).filter(models.User.id == id).first()


def get_user_by_email(db: Session, email: str) -> models.User:
def get_user_by_email(db: Session, email: str) -> Optional[models.User]:
return db.query(models.User).filter(models.User.email == email).first()


Expand Down Expand Up @@ -143,7 +143,7 @@ def create_spending(db: Session, spending: schemas.SpendingCreate, user_id: int)
db.add(db_spending)
db.commit()
db.refresh(db_spending)
create_transactions_from_spending(db, db_spending)
update_balances_from_spending(db, db_spending)
db.refresh(db_spending)
return db_spending

Expand All @@ -166,6 +166,29 @@ def get_spendings_by_category(db: Session, category_id: int):
)


################################################
# PAYMENTS
################################################


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_payments_by_group_id(db: Session, group_id: int):
return (
db.query(models.Payment)
.filter(models.Payment.group_id == group_id)
.limit(100)
.all()
)


################################################
# BUDGETS
################################################
Expand Down Expand Up @@ -236,15 +259,21 @@ def update_invite_status(
db.refresh(db_invite)
return db_invite


################################################
# REMINDERS
################################################

def create_payment_reminder(db: Session, payment_reminder: schemas.PaymentReminderCreate, sender_id: int):
db_reminder = models.PaymentReminder(sender_id=sender_id,

def create_payment_reminder(
db: Session, payment_reminder: schemas.PaymentReminderCreate, sender_id: int
):
db_reminder = models.PaymentReminder(
sender_id=sender_id,
receiver_id=payment_reminder.receiver_id,
group_id=payment_reminder.group_id)

group_id=payment_reminder.group_id,
)

if payment_reminder.message is not None:
db_reminder.message = payment_reminder.message

Expand All @@ -253,12 +282,13 @@ def create_payment_reminder(db: Session, payment_reminder: schemas.PaymentRemind
db.refresh(db_reminder)
return db_reminder


################################################
# TRANSACTIONS
# BALANCES
################################################


def create_transactions_from_spending(db: Session, spending: models.Spending):
def update_balances_from_spending(db: Session, spending: models.Spending):
group = get_group_by_id(db, spending.group_id)
balances = sorted(
get_balances_by_group_id(db, spending.group_id), key=lambda x: x.user_id
Expand All @@ -267,29 +297,31 @@ def create_transactions_from_spending(db: Session, spending: models.Spending):
# TODO: implement division strategy
# TODO: this truncates decimals
amount_per_member = spending.amount // len(members)
txs = []
for user, balance in zip(members, balances):
amount = -amount_per_member

if spending.owner_id == user.id:
amount += spending.amount

tx = models.Transaction(
from_user_id=spending.owner_id,
to_user_id=user.id,
amount=amount,
spending_id=spending.id,
)
txs.append(tx)
db.add(tx)
balance.current_balance += amount

db.commit()


################################################
# BALANCES
################################################
def update_balances_from_payment(db: Session, payment: models.Payment):
balances = get_balances_by_group_id(db, payment.group_id)

# Update payer balance
payer = get_user_by_id(db, payment.from_id)
payer_balance = next(filter(lambda x: x.user_id == payer.id, balances))
payer_balance.current_balance += payment.amount

# Update payee balance
payee = get_user_by_id(db, payment.to_id)
payee_balance = next(filter(lambda x: x.user_id == payee.id, balances))
payee_balance.current_balance -= payment.amount

db.commit()


def get_balances_by_group_id(db: Session, group_id: int) -> List[models.Balance]:
Expand Down
21 changes: 14 additions & 7 deletions src/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
REMINDER_TEMPLATE_ID = 2
DEFAULT_REMINDER = "No demores muucho con la deuda! :D"


class MailSender(ABC):
@abstractmethod
def send_invite(self, sender: str, receiver: str, group_name: str) -> bool:
pass

@abstractmethod
def send_reminder(self, sender: str, receiver: str, group_id: int, message: Optional[str]) -> bool:
def send_reminder(
self, sender: str, receiver: str, group_id: int, message: Optional[str]
) -> bool:
pass


Expand Down Expand Up @@ -51,9 +54,10 @@ def send_invite(
except ApiException as e:
error(f"Failed to send email with error: {e}")
return False

def send_reminder(
self, sender: str, receiver: str, group: schemas.Group, message: Optional[str]) -> bool:
self, sender: str, receiver: str, group: schemas.Group, message: Optional[str]
) -> bool:
configuration = sdk.Configuration()
configuration.api_key["api-key"] = API_KEY

Expand All @@ -63,11 +67,13 @@ def send_reminder(
params = {
"sender": sender,
"message": DEFAULT_REMINDER if message is None else message,
"landing_page": f"{BASE_URL}",
"landing_page": f"{BASE_URL}",
"group_name": group.name,
}

email = sdk.SendSmtpEmail(to=to, template_id=REMINDER_TEMPLATE_ID, params=params)
email = sdk.SendSmtpEmail(
to=to, template_id=REMINDER_TEMPLATE_ID, params=params
)

try:
response = api_instance.send_transac_email(email)
Expand All @@ -83,9 +89,10 @@ def send_invite(
self, sender: str, receiver: str, group: schemas.Group, token: str
) -> bool:
return True

def send_reminder(
self, sender: str, receiver: str, group: schemas.Group, message: Optional[str]) -> bool:
self, sender: str, receiver: str, group: schemas.Group, message: Optional[str]
) -> bool:
return True


Expand Down
70 changes: 64 additions & 6 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,55 @@ def list_group_spendings(db: DbDependency, user: UserDependency, group_id: int):
return crud.get_spendings_by_group_id(db, group_id)


################################################
# PAYMENTS
################################################


@app.post("/payment", status_code=HTTPStatus.CREATED)
def create_payment(
payment: schemas.PaymentCreate, db: DbDependency, user: UserDependency
):
group = crud.get_group_by_id(db, payment.group_id)

# Check creator, sender, and receiver are members of the group
check_group_exists_and_user_is_member(user.id, group)
check_group_exists_and_user_is_member(payment.from_id, group)
check_group_exists_and_user_is_member(payment.to_id, group)

if payment.from_id == payment.to_id:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="No se puede realizar un pago a uno mismo.",
)

if group.is_archived:
raise HTTPException(
status_code=HTTPStatus.NOT_ACCEPTABLE,
detail="El grupo esta archivado, no se pueden seguir agregando pagos.",
)

return crud.create_payment(db, payment)


@app.get("/payment")
def list_payments(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_payments_by_group_id(db, group_id)


@app.get("/group/{group_id}/payment")
def list_group_payments(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_payments_by_group_id(db, group_id)


################################################
# BUDGETS
################################################
Expand Down Expand Up @@ -480,33 +529,42 @@ def accept_invite(db: DbDependency, user: UserDependency, invite_token: str):
crud.add_user_to_group(db, user, target_group)
return crud.update_invite_status(db, target_invite, schemas.InviteStatus.ACCEPTED)


################################################
# REMINDERS
################################################


@app.post("/payment_reminder", status_code=HTTPStatus.CREATED)
def send_payment_reminder(db: DbDependency,
def send_payment_reminder(
db: DbDependency,
user: UserDependency,
mail: MailDependency,
payment_reminder: schemas.PaymentReminderCreate):
payment_reminder: schemas.PaymentReminderCreate,
):

receiver = crud.get_user_by_email(db, payment_reminder.receiver_email)
if receiver is None:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="No se encontro el usuario receptor."
status_code=HTTPStatus.NOT_FOUND,
detail="No se encontro el usuario receptor.",
)
group = crud.get_group_by_id(db, payment_reminder.group_id)
check_group_exists_and_user_is_member(receiver.id, group)
check_group_is_unarchived(group)
payment_reminder.receiver_id = receiver.id


sent_ok = mail.send_reminder(
sender=user.email, receiver=receiver.email, group=group, message=payment_reminder.message)
sender=user.email,
receiver=receiver.email,
group=group,
message=payment_reminder.message,
)

if sent_ok:
return crud.create_payment_reminder(db, payment_reminder, user.id)
else:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="No se pudo enviar recordatorio de pago al usuario."
status_code=HTTPStatus.BAD_REQUEST,
detail="No se pudo enviar recordatorio de pago al usuario.",
)
26 changes: 12 additions & 14 deletions src/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ class Spending(Base):
date: Mapped[datetime] = mapped_column(DateTime, default=func.now())


class Payment(Base):
__tablename__ = "payments"

id = Column(Integer, primary_key=True)
group_id = Column(ForeignKey("groups.id"))
from_id = Column(ForeignKey("users.id"))
to_id = Column(ForeignKey("users.id"))
amount = Column(Integer)
date: Mapped[datetime] = mapped_column(DateTime, default=func.now())


class Budget(Base):
__tablename__ = "budgets"

Expand All @@ -91,19 +102,6 @@ class Invite(Base):
creation_date: Mapped[datetime] = mapped_column(DateTime, default=func.now())


class Transaction(Base):
__tablename__ = "transactions"

id = Column(Integer, primary_key=True)
spending_id = Column(ForeignKey("spendings.id"))
from_user_id = Column(ForeignKey("users.id"))
to_user_id = Column(ForeignKey("users.id"))
date: Mapped[datetime] = mapped_column(DateTime, default=func.now())
amount = Column(Integer)

__table_args__ = (UniqueConstraint("spending_id", "to_user_id"),)


class Balance(Base):
__tablename__ = "balances"

Expand All @@ -114,6 +112,7 @@ class Balance(Base):

__table_args__ = (UniqueConstraint("user_id", "group_id"),)


class PaymentReminder(Base):
__tablename__ = "payment_reminders"

Expand All @@ -123,4 +122,3 @@ class PaymentReminder(Base):
group_id = Column(Integer, ForeignKey("groups.id"))
message = Column(String)
creation_date: Mapped[datetime] = mapped_column(DateTime, default=func.now())

Loading

0 comments on commit 3f5deab

Please sign in to comment.