1
|
# -*- coding: utf-8 -*-
|
2
|
|
3
|
from datetime import datetime, date, timedelta
|
4
|
from copy import copy
|
5
|
|
6
|
from django.utils.translation import ugettext_lazy as _
|
7
|
from django.contrib.auth.models import User
|
8
|
from django.db import models
|
9
|
|
10
|
from ..middleware.request import get_request
|
11
|
from calebasse.agenda import managers
|
12
|
from calebasse.utils import weeks_since_epoch
|
13
|
from interval import Interval
|
14
|
|
15
|
__all__ = (
|
16
|
'EventType',
|
17
|
'Event',
|
18
|
'EventWithAct',
|
19
|
)
|
20
|
|
21
|
class EventType(models.Model):
|
22
|
'''
|
23
|
Simple ``Event`` classifcation.
|
24
|
'''
|
25
|
class Meta:
|
26
|
verbose_name = u'Type d\'événement'
|
27
|
verbose_name_plural = u'Types d\'événement'
|
28
|
|
29
|
def __unicode__(self):
|
30
|
return self.label
|
31
|
|
32
|
label = models.CharField(_('label'), max_length=50)
|
33
|
rank = models.IntegerField(_('Sorting Rank'), null=True, blank=True, default=0)
|
34
|
|
35
|
|
36
|
class Event(models.Model):
|
37
|
'''
|
38
|
Container model for general agenda events
|
39
|
'''
|
40
|
objects = managers.EventManager()
|
41
|
|
42
|
title = models.CharField(_('Title'), max_length=32, blank=True)
|
43
|
description = models.TextField(_('Description'), max_length=100)
|
44
|
event_type = models.ForeignKey(EventType, verbose_name=u"Type d'événement")
|
45
|
creator = models.ForeignKey(User, verbose_name=_(u'Créateur'), blank=True, null=True)
|
46
|
create_date = models.DateTimeField(_(u'Date de création'), auto_now_add=True)
|
47
|
|
48
|
services = models.ManyToManyField('ressources.Service',
|
49
|
null=True, blank=True, default=None)
|
50
|
participants = models.ManyToManyField('personnes.People',
|
51
|
null=True, blank=True, default=None)
|
52
|
room = models.ForeignKey('ressources.Room', blank=True, null=True,
|
53
|
verbose_name=u'Salle')
|
54
|
|
55
|
start_datetime = models.DateTimeField(_('Début'), blank=True, null=True, db_index=True)
|
56
|
end_datetime = models.DateTimeField(_('Fin'), blank=True, null=True)
|
57
|
|
58
|
PERIODS = (
|
59
|
(1, u'Toutes les semaines'),
|
60
|
(2, u'Une semaine sur deux'),
|
61
|
(3, u'Une semaine sur trois'),
|
62
|
(4, 'Une semaine sur quatre'),
|
63
|
(5, 'Une semaine sur cinq')
|
64
|
)
|
65
|
OFFSET = range(0,4)
|
66
|
recurrence_week_day = models.PositiveIntegerField(default=0)
|
67
|
recurrence_week_offset = models.PositiveIntegerField(
|
68
|
choices=zip(OFFSET, OFFSET),
|
69
|
verbose_name=u"Décalage en semaines par rapport au 1/1/1970 pour le calcul de période",
|
70
|
default=0,
|
71
|
db_index=True)
|
72
|
recurrence_week_period = models.PositiveIntegerField(
|
73
|
choices=PERIODS,
|
74
|
verbose_name=u"Période en semaines",
|
75
|
default=None,
|
76
|
blank=True,
|
77
|
null=True,
|
78
|
db_index=True)
|
79
|
recurrence_end_date = models.DateField(
|
80
|
verbose_name=_(u'Fin de la récurrence'),
|
81
|
blank=True, null=True,
|
82
|
db_index=True)
|
83
|
|
84
|
class Meta:
|
85
|
verbose_name = u'Evénement'
|
86
|
verbose_name_plural = u'Evénements'
|
87
|
ordering = ('start_datetime', 'end_datetime', 'title')
|
88
|
|
89
|
def __init__(self, *args, **kwargs):
|
90
|
if kwargs.get('start_datetime') and not kwargs.has_key('recurrence_end_date'):
|
91
|
kwargs['recurrence_end_date'] = kwargs.get('start_datetime').date()
|
92
|
super(Event, self).__init__(*args, **kwargs)
|
93
|
|
94
|
def clean(self):
|
95
|
'''Initialize recurrence fields if they are not.'''
|
96
|
if self.start_datetime:
|
97
|
if self.recurrence_week_period:
|
98
|
if self.recurrence_end_date and self.recurrence_end_date < self.start_datetime.date():
|
99
|
self.recurrence_end_date = self.start_datetime.date()
|
100
|
self.recurrence_week_day = self.start_datetime.weekday()
|
101
|
self.recurrence_week_offset = weeks_since_epoch(self.start_datetime) % self.recurrence_week_period
|
102
|
|
103
|
def timedelta(self):
|
104
|
'''Distance between start and end of the event'''
|
105
|
return self.end_datetime - self.start_datetime
|
106
|
|
107
|
def today_occurence(self, today=None):
|
108
|
'''For a recurring event compute the today 'Event'.
|
109
|
|
110
|
The computed event is the fake one that you cannot save to the database.
|
111
|
'''
|
112
|
today = today or date.today()
|
113
|
if not self.is_recurring():
|
114
|
if today == self.start_datetime.date():
|
115
|
return self
|
116
|
else:
|
117
|
return None
|
118
|
if today.weekday() != self.recurrence_week_day:
|
119
|
return None
|
120
|
if weeks_since_epoch(today) % self.recurrence_week_period != self.recurrence_week_offset:
|
121
|
return None
|
122
|
if not (self.start_datetime.date() <= today <= self.recurrence_end_date):
|
123
|
return None
|
124
|
start_datetime = datetime.combine(today, self.start_datetime.timetz())
|
125
|
end_datetime = start_datetime + self.timedelta()
|
126
|
event = copy(self)
|
127
|
event.start_datetime = start_datetime
|
128
|
event.end_datetime = end_datetime
|
129
|
event.recurrence_end_date = None
|
130
|
event.recurrence_week_offset = None
|
131
|
event.recurrence_week_period = None
|
132
|
event.parent = self
|
133
|
def save(*args, **kwarks):
|
134
|
raise RuntimeError()
|
135
|
event.save = save
|
136
|
return event
|
137
|
|
138
|
def next_occurence(self, today=None):
|
139
|
'''Returns the next occurence after today.'''
|
140
|
today = today or date.today()
|
141
|
for occurence in self.all_occurences():
|
142
|
if occurence.start_datetime.date() > today:
|
143
|
return occurence
|
144
|
|
145
|
def is_recurring(self):
|
146
|
'''Is this event multiple ?'''
|
147
|
return self.recurrence_week_period is not None
|
148
|
|
149
|
def all_occurences(self, limit=90):
|
150
|
'''Returns all occurences of this event as a list or pair (start_datetime,
|
151
|
end_datetime).
|
152
|
|
153
|
limit - compute occurrences until limit days in the future
|
154
|
|
155
|
Default is to limit to 90 days.
|
156
|
'''
|
157
|
if self.recurrence_week_period:
|
158
|
day = self.start_datetime.date()
|
159
|
max_end_date = max(date.today(), self.start_datetime.date()) + timedelta(days=limit)
|
160
|
end_date = min(self.recurrence_end_date or max_end_date, max_end_date)
|
161
|
delta = timedelta(days=self.recurrence_week_period*7)
|
162
|
while day <= end_date:
|
163
|
yield self.today_occurence(day)
|
164
|
day += delta
|
165
|
else:
|
166
|
yield self
|
167
|
|
168
|
def to_interval(self):
|
169
|
return Interval(self.start_datetime, self.end_datetime)
|
170
|
|
171
|
def is_event_absence(self):
|
172
|
return False
|
173
|
|
174
|
def __unicode__(self):
|
175
|
return self.title
|
176
|
|
177
|
|
178
|
class EventWithActManager(managers.EventManager):
|
179
|
def create_patient_appointment(self, creator, title, patient,
|
180
|
doctors=[], act_type=None, service=None, start_datetime=None, end_datetime=None,
|
181
|
room=None, periodicity=1, until=False):
|
182
|
appointment = self.create_event(creator=creator,
|
183
|
title=title,
|
184
|
event_type=EventType(id=1),
|
185
|
participants=doctors,
|
186
|
services=[service],
|
187
|
start_datetime=start_datetime,
|
188
|
end_datetime=end_datetime,
|
189
|
room=room,
|
190
|
periodicity=periodicity,
|
191
|
until=until,
|
192
|
act_type=act_type,
|
193
|
patient=patient)
|
194
|
return appointment
|
195
|
|
196
|
|
197
|
class EventWithAct(Event):
|
198
|
'''An event corresponding to an act.'''
|
199
|
objects = EventWithActManager()
|
200
|
act_type = models.ForeignKey('ressources.ActType',
|
201
|
verbose_name=u'Type d\'acte')
|
202
|
patient = models.ForeignKey('dossiers.PatientRecord')
|
203
|
|
204
|
@property
|
205
|
def act(self):
|
206
|
return self.get_or_create_act()
|
207
|
|
208
|
def get_or_create_act(self, today=None):
|
209
|
from ..actes.models import Act, ActValidationState
|
210
|
today = today or self.start_datetime.date()
|
211
|
act, created = Act.objects.get_or_create(patient=self.patient,
|
212
|
parent_event=getattr(self, 'parent', self),
|
213
|
date=today,
|
214
|
act_type=self.act_type)
|
215
|
self.update_act(act)
|
216
|
if created:
|
217
|
ActValidationState.objects.create(act=act, state_name='NON_VALIDE',
|
218
|
author=self.creator, previous_state=None)
|
219
|
return act
|
220
|
|
221
|
def update_act(self, act):
|
222
|
'''Update an act to match details of the meeting'''
|
223
|
delta = self.timedelta()
|
224
|
duration = delta.seconds // 60
|
225
|
act._duration = duration
|
226
|
act.doctors = self.participants.select_subclasses()
|
227
|
act.act_type = self.act_type
|
228
|
act.patient = self.patient
|
229
|
act.date = self.start_datetime.date()
|
230
|
act.save()
|
231
|
|
232
|
def save(self, *args, **kwargs):
|
233
|
'''Force event_type to be patient meeting.'''
|
234
|
self.clean()
|
235
|
self.event_type = EventType(id=1)
|
236
|
super(EventWithAct, self).save(*args, **kwargs)
|
237
|
# list of occurences may have changed
|
238
|
from ..actes.models import Act
|
239
|
occurences = list(self.all_occurences())
|
240
|
acts = Act.objects.filter(parent_event=self)
|
241
|
occurences_by_date = dict((o.start_datetime.date(), o) for o in occurences)
|
242
|
acts_by_date = dict()
|
243
|
for a in acts:
|
244
|
# sanity check
|
245
|
assert a.date not in acts_by_date
|
246
|
acts_by_date[a.date] = a
|
247
|
for a in acts:
|
248
|
o = occurences_by_date.get(a.date)
|
249
|
if o:
|
250
|
o.update_act(a)
|
251
|
else:
|
252
|
if len(a.actvalidationstate_set.all()) > 1:
|
253
|
a.parent_event = None
|
254
|
a.save()
|
255
|
else:
|
256
|
a.delete()
|
257
|
participants = self.participants.select_subclasses()
|
258
|
for o in occurences:
|
259
|
if o.start_datetime.date() in acts_by_date:
|
260
|
continue
|
261
|
o.get_or_create_act()
|
262
|
|
263
|
def is_event_absence(self):
|
264
|
return self.act.is_absent()
|
265
|
|
266
|
def __unicode__(self):
|
267
|
kwargs = {
|
268
|
'patient': self.patient,
|
269
|
'start_datetime': self.start_datetime,
|
270
|
'act_type': self.act_type
|
271
|
}
|
272
|
kwargs['doctors'] = ', '.join(map(unicode, self.participants.all())) if self.id else ''
|
273
|
return u'Rdv le {start_datetime} de {patient} avec {doctors} pour ' \
|
274
|
'{act_type} ({act_type.id})'.format(**kwargs)
|