|
@@ -1,10 +1,14 @@
|
1
|
1
|
import datetime
|
2
|
2
|
from django.contrib.auth.models import User
|
3
|
3
|
from django.db import models
|
4
|
|
-from django.utils.dateformat import format
|
5
|
4
|
from django.utils.translation import gettext as _
|
6
|
5
|
from simple_history.models import HistoricalRecords
|
7
|
6
|
|
|
7
|
+
|
|
8
|
+THRESHOLD_VERY_SOON_CHOICES = ((1, '1 day'), (2, '2 days'), (3, '3 days'), (4, '4 days'), (5, '5 days'), (7, '7 days'), (9, '9 days'), (12, '12 days'), ) # REQ-38
|
|
9
|
+THRESHOLD_SOON_CHOICES = ((1, '1 day'), (2, '2 days'), (3, '3 days'), (5, '5 days'), (7, '7 days'), (10, '10 days'), (14, '14 days'), (21, ' days'), ) # REQ-39
|
|
10
|
+
|
|
11
|
+
|
8
|
12
|
# PROJECTSTATE (REQ-??)
|
9
|
13
|
#
|
10
|
14
|
PROJECTSTATE_OPEN = 0
|
|
@@ -75,6 +79,26 @@ COMMENTTYPE_CHOICES = (
|
75
|
79
|
|
76
|
80
|
# GENERAL Methods and Classes
|
77
|
81
|
#
|
|
82
|
+def get_pattuserprofile(user):
|
|
83
|
+ if user is None: # return a default profile if no (assigned_)user exist
|
|
84
|
+ profile = PattUserProfile()
|
|
85
|
+ else:
|
|
86
|
+ try:
|
|
87
|
+ profile = user.pattuserprofile
|
|
88
|
+ except PattUserProfile.DoesNotExist:
|
|
89
|
+ profile = PattUserProfile(user=user)
|
|
90
|
+ profile.save()
|
|
91
|
+ return profile
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+# USERPROFILE Model
|
|
95
|
+#
|
|
96
|
+class PattUserProfile(models.Model):
|
|
97
|
+ user = models.OneToOneField(User, unique=True, on_delete=models.CASCADE)
|
|
98
|
+ threshold_very_soon = models.IntegerField(default=4, choices=THRESHOLD_VERY_SOON_CHOICES) # REQ-38
|
|
99
|
+ threshold_soon = models.IntegerField(default=10, choices=THRESHOLD_SOON_CHOICES) # REQ-39
|
|
100
|
+
|
|
101
|
+
|
78
|
102
|
class ModelList(list):
|
79
|
103
|
def __init__(self, lst, chk=None, user=None):
|
80
|
104
|
if chk is None:
|
|
@@ -100,9 +124,6 @@ class Project(models.Model):
|
100
|
124
|
role_member = models.ManyToManyField(User, related_name='role_member', blank=True) # REQ-12
|
101
|
125
|
role_visitor = models.ManyToManyField(User, related_name='role_visitor', blank=True) # REQ-33
|
102
|
126
|
creation_date = models.DateTimeField(auto_now_add=True)
|
103
|
|
- days_late = models.IntegerField(default=0, choices=((-1, _('Off')), (0, 0), ))
|
104
|
|
- days_very_soon = models.IntegerField(default=3, choices=((-1, _('Off')), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (7, 7), (9, 9), ))
|
105
|
|
- days_soon = models.IntegerField(default=7, choices=((-1, _('Off')), (1, 1), (2, 2), (3, 3), (5, 5), (7, 7), (10, 10), (14, 14), ))
|
106
|
127
|
|
107
|
128
|
@property
|
108
|
129
|
def attachment_target_path(self):
|
|
@@ -142,11 +163,11 @@ class Project(models.Model):
|
142
|
163
|
# TASK Model
|
143
|
164
|
#
|
144
|
165
|
class Task(models.Model):
|
145
|
|
- FUSION_STATE_FINISHED = 0
|
146
|
|
- FUSION_STATE_NORMAL = 1
|
147
|
|
- FUSION_STATE_SOON = 2
|
148
|
|
- FUSION_STATE_VERY_SOON = 3
|
149
|
|
- FUSION_STATE_LATE = 4
|
|
166
|
+ WARNING_GROUP_OVERDUE = 0 # REQ-40
|
|
167
|
+ WARNING_GROUP_VERY_SOON = 1 # REQ-40
|
|
168
|
+ WARNING_GROUP_SOON = 2 # REQ-40
|
|
169
|
+ WARNING_GROUP_DEFAULT = 3 # REQ-40
|
|
170
|
+ WARNING_GROUP_DONE = 4 # REQ-40
|
150
|
171
|
#
|
151
|
172
|
SAVE_ON_CHANGE_FIELDS = ['state', 'priority', 'targetdate', 'progress', 'name', 'description', 'assigned_user', 'project']
|
152
|
173
|
#
|
|
@@ -194,39 +215,42 @@ class Task(models.Model):
|
194
|
215
|
rv.sort(reverse=True)
|
195
|
216
|
return rv
|
196
|
217
|
|
197
|
|
- def datafusion_state(self):
|
|
218
|
+ def days_before_targetdate(self): # REQ-40, REQ-43
|
|
219
|
+ if self.targetdate is None:
|
|
220
|
+ return float('Inf')
|
|
221
|
+ else:
|
|
222
|
+ if type(self.targetdate) == datetime.date:
|
|
223
|
+ targetdate = self.targetdate
|
|
224
|
+ else:
|
|
225
|
+ targetdate = datetime.datetime.strptime(self.targetdate, '%Y-%m-%d').date()
|
|
226
|
+ rv = targetdate - datetime.datetime.now().date()
|
|
227
|
+ return rv.days
|
|
228
|
+
|
|
229
|
+ def warning_group(self): # REQ-40
|
198
|
230
|
if self.state in [TASKSTATE_CANCELED, TASKSTATE_CLOSED, TASKSTATE_FINISHED]:
|
199
|
|
- return self.FUSION_STATE_FINISHED
|
|
231
|
+ return self.WARNING_GROUP_DONE
|
200
|
232
|
else:
|
201
|
|
- if self.targetdate is not None:
|
202
|
|
- if type(self.targetdate) == datetime.date:
|
203
|
|
- targetdate = self.targetdate
|
204
|
|
- else:
|
205
|
|
- targetdate = datetime.datetime.strptime(self.targetdate, '%Y-%m-%d').date()
|
206
|
|
- if targetdate - datetime.date.today() <= datetime.timedelta(self.project.days_late) and self.project.days_late >= 0:
|
207
|
|
- return self.FUSION_STATE_LATE
|
208
|
|
- elif targetdate - datetime.date.today() <= datetime.timedelta(self.project.days_very_soon) and self.project.days_very_soon >= 0:
|
209
|
|
- return self.FUSION_STATE_VERY_SOON
|
210
|
|
- elif targetdate - datetime.date.today() <= datetime.timedelta(self.project.days_soon) and self.project.days_soon >= 0:
|
211
|
|
- return self.FUSION_STATE_SOON
|
212
|
|
- return self.FUSION_STATE_NORMAL
|
|
233
|
+ dbt = self.days_before_targetdate()
|
|
234
|
+ if dbt <= 0:
|
|
235
|
+ return self.WARNING_GROUP_OVERDUE
|
|
236
|
+ if dbt <= get_pattuserprofile(self.assigned_user).threshold_very_soon:
|
|
237
|
+ return self.WARNING_GROUP_VERY_SOON
|
|
238
|
+ if dbt <= get_pattuserprofile(self.assigned_user).threshold_soon:
|
|
239
|
+ return self.WARNING_GROUP_SOON
|
|
240
|
+ return self.WARNING_GROUP_DEFAULT
|
213
|
241
|
|
214
|
242
|
@property
|
215
|
243
|
def class_by_state(self):
|
216
|
244
|
return {
|
217
|
|
- self.FUSION_STATE_FINISHED: 'task-finished',
|
218
|
|
- self.FUSION_STATE_NORMAL: 'task-normal',
|
219
|
|
- self.FUSION_STATE_VERY_SOON: 'task-very-soon',
|
220
|
|
- self.FUSION_STATE_SOON: 'task-soon',
|
221
|
|
- self.FUSION_STATE_LATE: 'task-late',
|
222
|
|
- }.get(self.datafusion_state())
|
223
|
|
-
|
224
|
|
- def sort_string(self):
|
225
|
|
- if self.targetdate:
|
226
|
|
- td = int(format(self.targetdate, 'U'))
|
227
|
|
- else:
|
228
|
|
- td = 999999999999
|
229
|
|
- return (100 - self.datafusion_state(), self.state, self.priority, td, self.progress, self.name)
|
|
245
|
+ self.WARNING_GROUP_DONE: 'task-finished',
|
|
246
|
+ self.WARNING_GROUP_DEFAULT: 'task-normal',
|
|
247
|
+ self.WARNING_GROUP_SOON: 'task-soon',
|
|
248
|
+ self.WARNING_GROUP_VERY_SOON: 'task-very-soon',
|
|
249
|
+ self.WARNING_GROUP_OVERDUE: 'task-late',
|
|
250
|
+ }.get(self.warning_group())
|
|
251
|
+
|
|
252
|
+ def sort_string(self): # REQ-41
|
|
253
|
+ return (self.warning_group(), self.priority, self.days_before_targetdate(), self.progress, self.name)
|
230
|
254
|
|
231
|
255
|
def __str__(self):
|
232
|
256
|
return 'Task #%d: %s' % (self.id, self.name)
|