/timepiece/tests/projection.py

https://bitbucket.org/copelco/django-timepiece/ · Python · 287 lines · 256 code · 13 blank · 18 comment · 6 complexity · 31af60480ffe4310c397a2b9e17a9fba MD5 · raw file

  1. import datetime
  2. import random
  3. from decimal import Decimal
  4. from django.conf import settings
  5. from django.core.urlresolvers import reverse
  6. from django.db.models import Sum
  7. from django.contrib.auth.models import User
  8. from timepiece import models as timepiece
  9. from timepiece import forms as timepiece_forms
  10. from timepiece.tests.base import TimepieceDataTestCase
  11. from dateutil import relativedelta
  12. from timepiece.projection import run_projection, user_weekly_assignments
  13. from timepiece import utils
  14. class ProjectionTest(TimepieceDataTestCase):
  15. def setUp(self):
  16. person = User.objects.create_user('test', 'a@b.com', 'abc')
  17. self.ps = self.create_person_schedule(data={'user': person})
  18. def log_time(self, assignment, delta=None, start=None):
  19. if delta:
  20. hours, minutes = delta
  21. else:
  22. hours = 4
  23. minutes = 0
  24. if not start:
  25. start = datetime.datetime.now()
  26. elif not isinstance(start, datetime.datetime):
  27. start = datetime.datetime.combine(start, datetime.time())
  28. end = start + datetime.timedelta(hours=hours, minutes=minutes)
  29. data = {'user': assignment.user,
  30. 'start_time': start,
  31. 'end_time': end,
  32. 'project': assignment.contract.project}
  33. return self.create_entry(data)
  34. def test_week_start(self):
  35. """ Test that all days Sunday through Saturday return Sunday """
  36. sunday = datetime.date(2011, 1, 16)
  37. self.assertEqual(sunday, utils.get_week_start(sunday))
  38. monday = datetime.date(2011, 1, 17)
  39. self.assertEqual(sunday, utils.get_week_start(monday))
  40. saturday = datetime.date(2011, 1, 22)
  41. self.assertEqual(sunday, utils.get_week_start(saturday))
  42. def test_generate_weeks(self):
  43. """ Test generation of full week ranges """
  44. # 2 weeks
  45. start = datetime.date(2011, 1, 16)
  46. end = datetime.date(2011, 1, 29)
  47. weeks = utils.generate_weeks(start=start, end=end)
  48. self.assertEqual(2, weeks.count())
  49. # 3 weeks
  50. start = datetime.date(2011, 1, 16)
  51. end = datetime.date(2011, 1, 30)
  52. weeks = utils.generate_weeks(start=start, end=end)
  53. self.assertEqual(3, weeks.count())
  54. # random weeks
  55. num = random.randint(5, 20)
  56. start = utils.get_week_start(datetime.date.today())
  57. end = start + datetime.timedelta(weeks=num - 1)
  58. weeks = utils.generate_weeks(start=start, end=end)
  59. self.assertEqual(num, weeks.count())
  60. def test_week_window(self):
  61. """ Test generation of weekly window with given date """
  62. #Tuesday
  63. day = datetime.date(2011, 2, 1)
  64. expected_start = datetime.date(2011, 1, 30)
  65. expected_end = datetime.date(2011, 2, 6)
  66. start, end = utils.get_week_window(day)
  67. self.assertEqual(start.toordinal(), expected_start.toordinal())
  68. self.assertEqual(end.toordinal(), expected_end.toordinal())
  69. def test_project_contract_remaining_weeks(self):
  70. """ Test calculation of contract remaining weeks """
  71. num = random.randint(5, 20)
  72. start = datetime.date.today()
  73. end = start + datetime.timedelta(weeks=num - 1)
  74. pc = self.create_project_contract({'num_hours': 100, 'end_date': end,
  75. 'start_date': start})
  76. self.assertEqual(num, pc.weeks_remaining.count())
  77. def test_weekly_commmitment(self):
  78. """ Test calculation of contract assignment's weekly commitment """
  79. start = utils.get_week_start()
  80. end = start + datetime.timedelta(weeks=2) - datetime.timedelta(days=1)
  81. ca = self._assign(start, end, hours=40)
  82. self.assertEqual(ca.weekly_commitment(start), 40)
  83. def test_weekly_commmitment_with_earlier_allocation(self):
  84. """ Test calculation of contract assignment's weekly commitment """
  85. # 1 week assignment, 20 hours
  86. start = utils.get_week_start()
  87. end = start + datetime.timedelta(weeks=1) - datetime.timedelta(days=1)
  88. ca1 = self._assign(start, end, hours=20)
  89. # allocate 20 hours to this assignment
  90. ca1.blocks.create(date=start, hours=20)
  91. # 1 week assignment, 20 hours
  92. ca2 = self._assign(start, end, hours=20)
  93. # only 20 hours left this week
  94. self.assertEqual(ca2.weekly_commitment(start), 20)
  95. def test_weekly_commmitment_with_look_ahead(self):
  96. """ Test later assignment's min hours factor into weekly commitment """
  97. # 1 week assignment, 40 hours
  98. start = utils.get_week_start()
  99. end = start + datetime.timedelta(weeks=1) - datetime.timedelta(days=1)
  100. ca1 = self._assign(start, end, hours=40)
  101. # 2 week assignment, 40 hours
  102. end = start + datetime.timedelta(weeks=1) - datetime.timedelta(days=1)
  103. ca2 = self._assign(start, end, hours=40)
  104. ca2.min_hours_per_week = 5
  105. ca2.save()
  106. self.assertEqual(ca1.weekly_commitment(start), 35)
  107. ca1.blocks.create(date=start, hours=35)
  108. self.assertEqual(ca2.weekly_commitment(start), 5)
  109. def test_weekly_commmitment_with_hours_worked(self):
  110. """ Test weekly commitment with previously logged hours """
  111. start = utils.get_week_start()
  112. end = start + datetime.timedelta(weeks=2) - datetime.timedelta(days=1)
  113. ca = self._assign(start, end, hours=30)
  114. self.log_time(ca, start=start, delta=(10, 0))
  115. self.assertEqual(ca.hours_worked, 10)
  116. self.assertEqual(ca.hours_remaining, 20)
  117. self.assertEqual(ca.weekly_commitment(start), 30)
  118. def test_weekly_commitment_over_remaining(self):
  119. # 1 week assignment, 20 hours
  120. start = utils.get_week_start()
  121. end = start + datetime.timedelta(weeks=1) - datetime.timedelta(days=1)
  122. ca = self._assign(start, end, hours=20)
  123. # only 20 hours left on assignment
  124. self.assertEqual(ca.weekly_commitment(start), 20)
  125. def _assign(self, start=None, end=None, hours=30):
  126. pc = self.create_project_contract({'num_hours': hours,
  127. 'start_date': start,
  128. 'end_date': end})
  129. ca = self.create_contract_assignment({'contract': pc,
  130. 'user': self.ps.user,
  131. 'num_hours': hours})
  132. return ca
  133. def test_assignment_active_ends_mid_week(self):
  134. """ Test manager returns assignments that end before end of window """
  135. start = utils.get_week_start() - datetime.timedelta(days=2)
  136. end = start + datetime.timedelta(weeks=1)
  137. ca = self._assign(start, end)
  138. week = utils.get_week_start()
  139. next_week = week + datetime.timedelta(weeks=1)
  140. assignments = timepiece.ContractAssignment.objects
  141. assignments = assignments.active_during_week(week, next_week)
  142. self.assertTrue(assignments.filter(pk=ca.pk).exists())
  143. def test_assignment_active_starts_mid_week(self):
  144. """ Test manager returns assignments that start before window """
  145. start = utils.get_week_start() + datetime.timedelta(days=2)
  146. end = start + datetime.timedelta(weeks=2)
  147. ca = self._assign(start, end)
  148. week = utils.get_week_start()
  149. next_week = week + datetime.timedelta(weeks=1)
  150. assignments = timepiece.ContractAssignment.objects
  151. assignments = assignments.active_during_week(week, next_week)
  152. self.assertTrue(assignments.filter(pk=ca.pk).exists())
  153. def test_assignment_active_within_week(self):
  154. """ Test manager returns assignments that contain entire week """
  155. start = utils.get_week_start() - datetime.timedelta(weeks=1)
  156. end = start + datetime.timedelta(weeks=3)
  157. ca = self._assign(start, end)
  158. week = utils.get_week_start()
  159. next_week = week + datetime.timedelta(weeks=1)
  160. assignments = timepiece.ContractAssignment.objects
  161. assignments = assignments.active_during_week(week, next_week)
  162. self.assertTrue(assignments.filter(pk=ca.pk).exists())
  163. def test_no_remaining_hours(self):
  164. """ Gurantee no overcommittment """
  165. # 1 week, 40 hours
  166. start = utils.get_week_start()
  167. end = start + datetime.timedelta(weeks=1) - datetime.timedelta(days=1)
  168. ca1 = self._assign(start, end, hours=40)
  169. ca1.blocks.create(date=start, hours=40)
  170. self.assertEqual(ca1.weekly_commitment(start), 0)
  171. # 2 weeks, 40 hours
  172. end = start + datetime.timedelta(weeks=2) - datetime.timedelta(days=1)
  173. ca2 = self._assign(start, end, hours=40)
  174. self.assertEqual(ca2.weekly_commitment(start), 0)
  175. def test_single_assignment_projection(self):
  176. # 2 weeks, 60 hours
  177. start = utils.get_week_start()
  178. end = start + datetime.timedelta(weeks=2) - datetime.timedelta(days=1)
  179. ca = self._assign(start, end, hours=60)
  180. run_projection()
  181. self.assertEqual(60, ca.blocks.aggregate(s=Sum('hours'))['s'])
  182. def test_min_hours_per_week_weighted(self):
  183. """
  184. Test minimum hours/week with weighting based on assignment end date
  185. """
  186. start = utils.get_week_start()
  187. end = start + datetime.timedelta(weeks=1)
  188. ca1 = self._assign(start, end, hours=40)
  189. ca2 = self._assign(start, end + datetime.timedelta(days=1), hours=40)
  190. ca1.min_hours_per_week = 30
  191. ca1.save()
  192. ca2.min_hours_per_week = 30
  193. ca2.save()
  194. run_projection()
  195. projection = ca1.blocks.filter(date=start).aggregate(s=Sum('hours'))
  196. self.assertEqual(30, projection['s'])
  197. projection = ca2.blocks.filter(date=start).aggregate(s=Sum('hours'))
  198. self.assertEqual(10, projection['s'])
  199. def test_unallocated_hours(self):
  200. """ Test unallocated hours calculation """
  201. start = utils.get_week_start()
  202. end = start + datetime.timedelta(weeks=2) - datetime.timedelta(days=1)
  203. ca = self._assign(start, end, hours=40)
  204. unallocated_hours = ca.unallocated_hours_for_week(start)
  205. self.assertEqual(unallocated_hours, 40)
  206. ca.blocks.create(date=start, hours=5)
  207. unallocated_hours = ca.unallocated_hours_for_week(start)
  208. self.assertEqual(unallocated_hours, 35)
  209. def test_this_weeks_priority_type(self):
  210. """ Test categories for this week. """
  211. start = utils.get_week_start(datetime.date.today())
  212. end = start + datetime.timedelta(weeks=1)
  213. ca_starting = self._assign(start, end, hours=40)
  214. self.assertEqual('starting', ca_starting.this_weeks_priority_type)
  215. end = utils.get_week_start()
  216. start = end - datetime.timedelta(days=2)
  217. ca_ending = self._assign(start, end, hours=40)
  218. self.assertEqual('ending', ca_ending.this_weeks_priority_type)
  219. start = utils.get_week_start()
  220. end = start + datetime.timedelta(days=6)
  221. ca_starting_ending = self._assign(start, end, hours=40)
  222. self.assertEqual('ending', ca_starting_ending.this_weeks_priority_type)
  223. start = utils.get_week_start() - datetime.timedelta(days=1)
  224. end = start + datetime.timedelta(days=9)
  225. ca_ongoing = self._assign(start, end, hours=40)
  226. self.assertEqual('ongoing', ca_ongoing.this_weeks_priority_type)
  227. ## Need to test order goes ending, starting, ongoing.
  228. assignments = timepiece.ContractAssignment.objects.sort_by_priority()
  229. def test_this_weeks_allocations(self):
  230. # 2 weeks, 60 hours
  231. start = utils.get_week_start()
  232. end = start + datetime.timedelta(weeks=2) - datetime.timedelta(days=1)
  233. ca = self._assign(start, end, hours=20)
  234. person = User.objects.create_user('test2', 'a@b.com', 'abc')
  235. ps = self.create_person_schedule(data={'user': person})
  236. run_projection()
  237. assignments = timepiece.AssignmentAllocation.objects.during_this_week(
  238. self.ps.user)
  239. self.assertEquals(assignments.count(), 1)
  240. assignments = timepiece.AssignmentAllocation.objects.during_this_week(
  241. person)
  242. self.assertEquals(assignments.count(), 0)
  243. ca_2 = self._assign(start, end, hours=30)
  244. run_projection()
  245. assignments = timepiece.AssignmentAllocation.objects.during_this_week(
  246. self.ps.user)
  247. self.assertEquals(assignments.count(), 2)
  248. def test_this_weeks_hours(self):
  249. start = utils.get_week_start()
  250. end = start + datetime.timedelta(weeks=2) - datetime.timedelta(days=1)
  251. ca = self._assign(start, end, hours=60)
  252. run_projection()
  253. self.log_time(ca, start=start, delta=(10, 0))
  254. assignments = timepiece.AssignmentAllocation.objects.during_this_week(
  255. self.ps.user)
  256. self.assertEquals(assignments.count(), 1)
  257. assignment = assignments[0]
  258. self.assertEquals(assignment.hours_worked, 10)
  259. self.assertEquals(assignment.hours_left, assignment.hours - 10)