Skip to content

Commit

Permalink
consider bill detail outside timehseet range as well for weird missions
Browse files Browse the repository at this point in the history
add tests on billing control data
  • Loading branch information
digitalfox committed Apr 13, 2024
1 parent 7d3e576 commit c0b2a04
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 5 deletions.
85 changes: 81 additions & 4 deletions billing/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@
@author: Sébastien Renard ([email protected])
@license: AGPL v3 or newer (http://www.gnu.org/licenses/agpl-3.0.html)
"""
from datetime import date
import json

from django.test import TestCase, TransactionTestCase, RequestFactory
from django.db import IntegrityError
from django.urls import reverse
from django.contrib.auth.models import User
from django.utils.translation import gettext as _

from crm.models import Supplier, Client
from crm.models import Supplier, Subsidiary
from billing.models import SupplierBill, ClientBill, BillDetail
from leads.models import Lead
from staffing.models import Timesheet, Mission, FinancialCondition
from people.models import Consultant
from billing.views import pre_billing
from core.tests import PYDICI_FIXTURES, setup_test_user_features, TEST_USERNAME
from core.utils import previousMonth
from core.utils import previousMonth, nextMonth
from billing.utils import get_client_billing_control_pivotable_data

from datetime import date


class BillingModelTest(TransactionTestCase):
Expand Down Expand Up @@ -211,3 +213,78 @@ def test_pre_billing(self):
self.assertFalse(r.context["internal_billing"])
self.assertEqual(len(r.context["time_spent_billing"]), 1)

class TestBillingUtils(TestCase):
fixtures = PYDICI_FIXTURES
def setUp(self):
setup_test_user_features()
self.test_user = User.objects.get(username=TEST_USERNAME)

def test_get_client_billing_control_pivotable_data(self):
c = Consultant.objects.first()
d = json.loads(get_client_billing_control_pivotable_data())
self.assertEqual(len(d), 13) # Default test fixtures

s = Subsidiary(name="test", code="T")
s.save()
d = json.loads(get_client_billing_control_pivotable_data(filter_on_subsidiary=s))
self.assertEqual(len(d), 0) # new subsidiary, empty set

l = Lead(subsidiary=s, client_id=1)
l.save()
d = json.loads(get_client_billing_control_pivotable_data(filter_on_lead=l))
self.assertEqual(len(d), 0) # new lead, empty set

m = Mission(lead=l, subsidiary=s, nature="PROD", probability=100, billing_mode="TIME_SPENT")
m.save()
FinancialCondition(consultant=c, mission=m, daily_rate=1000).save()
self.assertEqual(len(d), 0) # new mission but no timesheet, empty set

Timesheet(mission=m, consultant=c, working_date=date.today(), charge=1).save()
d = json.loads(get_client_billing_control_pivotable_data(filter_on_lead=l))
self.assertEqual(len(d), 2) # now, we have timesheet, so we have billing amount with 0
self.assertEqual(sum([x[_("amount")] for x in d]), 1000)

# add client bill detail
bill = ClientBill(lead=l, creation_date=date.today(), state="0_PROPOSED")
bill.save()
BillDetail(bill=bill, consultant=c, mission=m, quantity=1, unit_price=1000, month=date.today().replace(day=1)).save()
bill.save()
d = json.loads(get_client_billing_control_pivotable_data(filter_on_lead=l))
self.assertEqual(len(d), 2) # Still 2 items
self.assertEqual(sum([x[_("amount")] for x in d]), 0) # we billed what we did

# add bills outside timesheet window
BillDetail(bill=bill, consultant=c, mission=m, quantity=3, unit_price=1000, month=nextMonth(date.today())).save()
BillDetail(bill=bill, consultant=c, mission=m, quantity=4, unit_price=1000, month=previousMonth(date.today())).save()
d = json.loads(get_client_billing_control_pivotable_data(filter_on_lead=l))
self.assertEqual(len(d), 6) # activity and bill for 3 months
self.assertEqual(sum([x[_("amount")] for x in d]), -7000) # we over bill by 7 * 1000

# add timesheet according billing
Timesheet(mission=m, consultant=c, working_date=nextMonth(date.today()), charge=3).save()
Timesheet(mission=m, consultant=c, working_date=previousMonth(date.today()), charge=4).save()
d = json.loads(get_client_billing_control_pivotable_data(filter_on_lead=l))
print(json.dumps(d, indent=4))
self.assertEqual(len(d), 6) # activity and bill for 3 months
self.assertEqual(sum([x[_("amount")] for x in d]), 0) # we billed what we did

# And new, add a fixed price mission
m2 = Mission(lead=l, subsidiary=s, nature="PROD", probability=100, price=5, billing_mode="FIXED_PRICE")
m2.save()
FinancialCondition(consultant=c, mission=m2, daily_rate=1000).save()
BillDetail(bill=bill, mission=m2, quantity=1, unit_price=3000).save()
d = json.loads(get_client_billing_control_pivotable_data(filter_on_lead=l))
self.assertEqual(len(d), 7) # activity and bill for 3 months and 1 fixed price bill
self.assertEqual(sum([x[_("amount")] for x in d]), -3000) # we bill in advance

# Add timesheet on fixed price mission
Timesheet(mission=m2, consultant=c, working_date=date.today(), charge=2).save()
d = json.loads(get_client_billing_control_pivotable_data(filter_on_lead=l))
self.assertEqual(len(d), 8) # 6 + and 1 activity and 1 bill for fixed price mission
self.assertEqual(sum([x[_("amount")] for x in d]), -1000) # Still one day of advance

# We spent too much time, but it won't be billed as it's a fixe price mission
Timesheet(mission=m2, consultant=c, working_date=nextMonth(date.today()), charge=8).save()
d = json.loads(get_client_billing_control_pivotable_data(filter_on_lead=l))
self.assertEqual(len(d), 9) # 6 + and 2 activities and 1 bill for fixed price mission
self.assertEqual(sum([x[_("amount")] for x in d]), 2000) # We should have 2K to bill, no more
19 changes: 18 additions & 1 deletion billing/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,22 @@ def get_client_billing_control_pivotable_data(filter_on_subsidiary=None, filter_
mission_fixed_price_data[_("amount")] = -float(billDetail.amount or 0)
data.append(mission_fixed_price_data)
# Add done work and time spent bills
timesheet_timespan = list(mission.timesheet_set.dates("working_date", "month", order="ASC"))
billing_timespan = list(mission.billdetail_set.dates("month", "month", order="ASC"))
if timesheet_timespan and billing_timespan:
start = min(timesheet_timespan[0], billing_timespan[0])
end = max(timesheet_timespan[-1], billing_timespan[-1])
elif timesheet_timespan and not billing_timespan:
start = timesheet_timespan[0]
end = timesheet_timespan[-1]
elif not timesheet_timespan and billing_timespan:
start = billing_timespan[0]
end = billing_timespan[-1]
else:
continue
consultants = Consultant.objects.filter(timesheet__mission=mission).distinct()
for month in mission.timesheet_set.dates("working_date", "month", order="ASC"):
month = start
while month <= end:
next_month = nextMonth(month)
for consultant in consultants:
mission_month_consultant_data = mission_data.copy()
Expand All @@ -215,6 +229,7 @@ def get_client_billing_control_pivotable_data(filter_on_subsidiary=None, filter_
mission_month_consultant_data[_("amount")] = turnover
mission_month_consultant_data[_("type")] = _("Done work")
data.append(mission_month_consultant_data)
#TODO: move this out this loop. Currently, we missed bills outside timesheet range. We should iterate on all BilledDetail objects of this mission
if mission.billing_mode == "TIME_SPENT":
# Add bills for time spent mission
mission_month_consultant_data = mission_month_consultant_data.copy()
Expand All @@ -225,6 +240,8 @@ def get_client_billing_control_pivotable_data(filter_on_subsidiary=None, filter_
mission_month_consultant_data[_("type")] = _("Service bill")
data.append(mission_month_consultant_data)

month = next_month

return json.dumps(data)


Expand Down

0 comments on commit c0b2a04

Please sign in to comment.