Skip to content

Commit

Permalink
fix(Expense): add default value for expense fields
Browse files Browse the repository at this point in the history
- Added `default=Decimal("0.00")` to the expense-related fields in `invoice_total.py` to ensure proper default values are set.
- This change prevents issues when the expense fields are left unset, avoiding errors in calculations or summaries.

test(total): add tests for expense service logic

- Implemented `TestExpenseService` in `test_service.py` to cover and validate the logic associated with handling expenses.
- Ensures that the new default behavior for expense fields works as expected, adding more robust test coverage for this functionality.
  • Loading branch information
MohmdFo committed Sep 6, 2024
1 parent 957d598 commit cd2d014
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 93 deletions.
24 changes: 15 additions & 9 deletions sage_invoice/models/invoice_total.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,63 @@
from decimal import Decimal

from django.db import models
from django.utils.translation import gettext_lazy as _


class Expense(models.Model):
subtotal = models.DecimalField(
verbose_name=_("Subtotal"),
max_digits=10,
decimal_places=2,
verbose_name=_("Subtotal"),
default=Decimal("0.00"),
help_text=_("The sum of all item totals."),
db_comment="Sum of all item totals in the invoice",
)
tax_percentage = models.DecimalField(
verbose_name=_("Tax Percentage"),
max_digits=5,
decimal_places=2,
default=0,
verbose_name=_("Tax Percentage"),
default=Decimal("0.00"),
help_text=_("The tax percentage applied to the invoice."),
db_comment="The percentage of tax applied to the invoice",
)
discount_percentage = models.DecimalField(
verbose_name=_("Discount Percentage"),
max_digits=5,
decimal_places=2,
default=0,
verbose_name=_("Discount Percentage"),
default=Decimal("0.00"),
help_text=_("The discount percentage applied to the invoice."),
db_comment="The percentage of discount applied to the invoice",
)
tax_amount = models.DecimalField(
verbose_name=_("Tax Amount"),
max_digits=10,
decimal_places=2,
verbose_name=_("Tax Amount"),
default=Decimal("0.00"),
help_text=_("The calculated tax amount."),
db_comment="The total tax amount calculated based on the subtotal and tax percentage",
)
discount_amount = models.DecimalField(
verbose_name=_("Discount Amount"),
max_digits=10,
decimal_places=2,
verbose_name=_("Discount Amount"),
default=Decimal("0.00"),
help_text=_("The calculated discount amount."),
db_comment="The total discount amount calculated based on the subtotal and discount percentage",
)
total_amount = models.DecimalField(
verbose_name=_("Total Amount"),
max_digits=10,
decimal_places=2,
verbose_name=_("Total Amount"),
default=Decimal("0.00"),
help_text=_("The final total after applying tax and discount."),
db_comment="The final total amount after tax and discount are applied",
)
invoice = models.OneToOneField(
"Invoice",
verbose_name=_("Invoice"),
on_delete=models.CASCADE,
related_name="total",
verbose_name=_("Invoice"),
help_text=_("The invoice associated with this total."),
db_comment="Reference to the associated invoice",
)
Expand Down
10 changes: 5 additions & 5 deletions sage_invoice/service/total.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
class ExpenseService:
"""Service class to handle calculations and saving for Expense."""

def calculate_and_save(self, invoice_total: dict, *args, **kwargs):
def calculate_and_save(self, invoice_total, *args, **kwargs):
"""Calculate the subtotal, tax amount, discount amount, and total
amount and then save the instance.
amount, and then save the instance.
"""
# Calculate subtotal from related items
# Calculate subtotal from related items, ensure it's not None
invoice_total.subtotal = sum(
item.total_price for item in invoice_total.invoice.items.all()
)
Expand All @@ -34,5 +34,5 @@ def calculate_and_save(self, invoice_total: dict, *args, **kwargs):
- invoice_total.discount_amount
)

# Save the Expense instance
invoice_total.save_base(*args, **kwargs)
# Save the Expense instance using the standard save method
invoice_total.save(*args, **kwargs)
158 changes: 79 additions & 79 deletions sage_invoice/tests/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,82 +147,82 @@ def test_render_quotation_with_items(self, template_discovery):
assert rendered_content == "Rendered content"


# class TestExpenseService:

# @pytest.fixture
# def invoice_category(self, db):
# """Fixture to create a real InvoiceCategory instance."""
# return InvoiceCategory.objects.create(
# title="Default Category", description="A default category for testing."
# )

# @pytest.fixture
# def invoice(self, db, invoice_category):
# """Fixture to create a real Invoice instance with items."""
# invoice = Invoice.objects.create(
# title="Test Invoice",
# invoice_date="2024-08-25",
# customer_name="John Doe",
# customer_email="john.doe@example.com",
# status="unpaid",
# category=invoice_category,
# due_date="2024-09-01",
# )
# invoice.items.create(
# description="Item 1", quantity=1, unit_price=Decimal("100.00")
# )
# invoice.items.create(
# description="Item 2", quantity=2, unit_price=Decimal("50.00")
# )
# return invoice

# @pytest.fixture
# def invoice_total(self, invoice):
# """Fixture to create an Expense instance."""
# return Expense.objects.create(
# tax_percentage=Decimal("10.00"),
# discount_percentage=Decimal("5.00"),
# invoice=invoice,
# )

# def test_calculate_and_save(self, invoice_total):
# """Test the calculate_and_save method of ExpenseService."""
# service = ExpenseService()

# with mock.patch.object(Expense, "save_base") as mock_save_base:
# service.calculate_and_save(invoice_total)

# assert invoice_total.subtotal == Decimal("200.00")
# assert invoice_total.tax_amount == Decimal("20.00")
# assert invoice_total.discount_amount == Decimal("10.00")
# assert invoice_total.total_amount == Decimal("210.00")
# mock_save_base.assert_called_once()

# def test_calculate_and_save_with_no_items(self, db, invoice_category):
# """Test calculate_and_save when the invoice has no items."""
# invoice = Invoice.objects.create(
# title="Empty Invoice",
# invoice_date="2024-08-25",
# customer_name="John Doe",
# customer_email="john.doe@example.com",
# status="unpaid",
# category=invoice_category,
# due_date="2024-09-01",
# )

# invoice_total = Expense.objects.create(
# tax_percentage=Decimal("10.00"),
# discount_percentage=Decimal("5.00"),
# invoice=invoice,
# )

# service = ExpenseService()

# with mock.patch.object(Expense, "save_base") as mock_save_base:
# service.calculate_and_save(invoice_total)

# assert invoice_total.subtotal == Decimal("0.00")
# assert invoice_total.tax_amount == Decimal("0.00")
# assert invoice_total.discount_amount == Decimal("0.00")
# assert invoice_total.total_amount == Decimal("0.00")
# mock_save_base.assert_called_once()
class TestExpenseService:

@pytest.fixture
def invoice_category(self, db):
"""Fixture to create a real InvoiceCategory instance."""
return InvoiceCategory.objects.create(
title="Default Category", description="A default category for testing."
)

@pytest.fixture
def invoice(self, db, invoice_category):
"""Fixture to create a real Invoice instance with items."""
invoice = Invoice.objects.create(
title="Test Invoice",
invoice_date="2024-08-25",
customer_name="John Doe",
customer_email="john.doe@example.com",
status="unpaid",
category=invoice_category,
due_date="2024-09-01",
)
invoice.items.create(
description="Item 1", quantity=1, unit_price=Decimal("100.00")
)
invoice.items.create(
description="Item 2", quantity=2, unit_price=Decimal("50.00")
)
return invoice

@pytest.fixture
def invoice_total(self, invoice):
"""Fixture to create an Expense instance."""
return Expense.objects.create(
tax_percentage=Decimal("10.00"),
discount_percentage=Decimal("5.00"),
invoice=invoice,
)

def test_calculate_and_save(self, invoice_total):
"""Test the calculate_and_save method of ExpenseService."""
service = ExpenseService()

with mock.patch.object(Expense, "save") as mock_save:
service.calculate_and_save(invoice_total)

assert invoice_total.subtotal == Decimal("200.00")
assert invoice_total.tax_amount == Decimal("20.00")
assert invoice_total.discount_amount == Decimal("10.00")
assert invoice_total.total_amount == Decimal("210.00")
mock_save.assert_called_once()

def test_calculate_and_save_with_no_items(self, db, invoice_category):
"""Test calculate_and_save when the invoice has no items."""
invoice = Invoice.objects.create(
title="Empty Invoice",
invoice_date="2024-08-25",
customer_name="John Doe",
customer_email="john.doe@example.com",
status="unpaid",
category=invoice_category,
due_date="2024-09-01",
)

invoice_total = Expense.objects.create(
tax_percentage=Decimal("10.00"),
discount_percentage=Decimal("5.00"),
invoice=invoice,
)

service = ExpenseService()

with mock.patch.object(Expense, "save") as mock_save:
service.calculate_and_save(invoice_total)

assert invoice_total.subtotal == Decimal("0.00")
assert invoice_total.tax_amount == Decimal("0.00")
assert invoice_total.discount_amount == Decimal("0.00")
assert invoice_total.total_amount == Decimal("0.00")
mock_save.assert_called_once()

0 comments on commit cd2d014

Please sign in to comment.