Project

General

Profile

Download (42.7 KB) Statistics
| Branch: | Tag: | Revision:

calebasse / calebasse / dossiers / models.py @ feffc421

1
# -*- coding: utf-8 -*-
2

    
3
import logging
4
import os
5

    
6
from datetime import datetime, date
7
from dateutil.relativedelta import relativedelta
8
from cPickle import loads, dumps
9

    
10
from django.conf import settings
11
from django.db import models
12
from django.db.models import Min, Max, Q
13
from django.contrib.auth.models import User
14
from django.core.validators import MinValueValidator
15

    
16
from calebasse.choices import TYPE_OF_CONTRACT_CHOICES, DEFICIENCY_CHOICES
17
from calebasse.models import PhoneNumberField, ZipCodeField
18
from calebasse.personnes.models import People
19
from calebasse.ressources.models import (ServiceLinkedAbstractModel,
20
        NamedAbstractModel)
21
from calebasse.actes.models import Act
22

    
23
from ..middleware.request import get_request
24

    
25
DEFAULT_ACT_NUMBER_DIAGNOSTIC = 6
26
DEFAULT_ACT_NUMBER_TREATMENT = 30
27
DEFAULT_ACT_NUMBER_PROLONGATION = 10
28
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_DAYS = 0
29
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_MONTHS = 0
30
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_YEARS = 1
31

    
32
logger = logging.getLogger('calebasse.dossiers')
33

    
34

    
35
class TransportPrescriptionLog(models.Model):
36
    patient = models.ForeignKey('dossiers.PatientRecord',
37
        verbose_name=u'Dossier patient')
38
    created = models.DateTimeField(u'Création', auto_now_add=True)
39
    choices = models.CharField(max_length = 4096, null=True, blank=True)
40

    
41
    def get_choices(self):
42
        if not self.choices:
43
            return dict()
44
        return loads(str(self.choices), protocol = 1)
45

    
46
    def set_choices(self, choices=None):
47
        if choices and isinstance(choices, dict):
48
            self.choices = dumps(choices, protocol = 1)
49

    
50

    
51
class HealthCare(models.Model):
52

    
53
    class Meta:
54
        app_label = 'dossiers'
55

    
56
    start_date = models.DateField(verbose_name=u"Date de début")
57
    request_date = models.DateField(verbose_name=u"Date de demande",
58
        blank=True, null=True)
59
    agree_date = models.DateField(verbose_name=u"Date d'accord",
60
        blank=True, null=True)
61
    insist_date = models.DateField(verbose_name=u"Date de relance",
62
        blank=True, null=True)
63
    patient = models.ForeignKey('dossiers.PatientRecord',
64
        verbose_name=u'Dossier patient')
65
    created = models.DateTimeField(u'Création', auto_now_add=True)
66
    author = \
67
        models.ForeignKey(User,
68
        verbose_name=u'Auteur', blank=True, null=True)
69
    comment = models.TextField(max_length=3000, blank=True, null=True, verbose_name=u"Commentaire")
70

    
71
    def get_nb_acts_cared(self):
72
        return len(self.act_set.all())
73

    
74

    
75
class CmppHealthCareDiagnostic(HealthCare):
76

    
77
    class Meta:
78
        app_label = 'dossiers'
79

    
80
    act_number = models.IntegerField(default=DEFAULT_ACT_NUMBER_DIAGNOSTIC, verbose_name=u"Nombre d'actes couverts")
81
    end_date = models.DateField(verbose_name=u"Date de fin",
82
        blank=True, null=True)
83

    
84
    def get_act_number(self):
85
        return self.act_number
86

    
87
    def set_act_number(self, value):
88
        if value < self.get_nb_acts_cared():
89
            raise Exception("La valeur doit être supérieur au "
90
                "nombre d'actes déjà pris en charge")
91
        self.act_number = value
92
        self.save()
93

    
94
    def save(self, **kwargs):
95
        self.start_date = \
96
            datetime(self.start_date.year, self.start_date.month,
97
                self.start_date.day)
98
        super(CmppHealthCareDiagnostic, self).save(**kwargs)
99

    
100

    
101
class CmppHealthCareTreatment(HealthCare):
102

    
103
    class Meta:
104
        app_label = 'dossiers'
105

    
106
    act_number = models.IntegerField(default=DEFAULT_ACT_NUMBER_TREATMENT,
107
            verbose_name=u"Nombre d'actes couverts")
108
    end_date = models.DateField(verbose_name=u"Date de fin",
109
        blank=True, null=True)
110
    prolongation = models.IntegerField(default=0,
111
            verbose_name=u'Prolongation')
112
    prolongation_date = models.DateField(verbose_name=u"Date de prolongation",
113
        blank=True, null=True)
114

    
115
    def get_act_number(self):
116
        if self.is_extended():
117
            return self.act_number + self.prolongation
118
        return self.act_number
119

    
120
    def set_act_number(self, value):
121
        if value < self.get_nb_acts_cared():
122
            raise Exception("La valeur doit être supérieur au "
123
                "nombre d'actes déjà pris en charge")
124
        self.act_number = value
125
        self.save()
126

    
127
    def is_extended(self):
128
        if self.prolongation > 0:
129
            return True
130
        return False
131

    
132
    def add_prolongation(self, value=None):
133
        if not value:
134
            value = DEFAULT_ACT_NUMBER_PROLONGATION
135
        if self.is_extended():
136
            raise Exception(u'Prise en charge déja prolongée')
137
        self.prolongation = value
138
        self.save()
139

    
140
    def del_prolongation(self):
141
        pass
142

    
143
    def save(self, **kwargs):
144
        self.start_date = \
145
            datetime(self.start_date.year, self.start_date.month,
146
                self.start_date.day)
147
        if not self.end_date:
148
            self.end_date = self.start_date + \
149
                relativedelta(years=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_YEARS) + \
150
                relativedelta(months=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_MONTHS) + \
151
                relativedelta(days=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_DAYS-1)
152
        super(CmppHealthCareTreatment, self).save(**kwargs)
153

    
154

    
155
class SessadHealthCareNotification(HealthCare):
156

    
157
    class Meta:
158
        app_label = 'dossiers'
159

    
160
    end_date = models.DateField(verbose_name=u"Date de fin",
161
        blank=True, null=True)
162

    
163
    def save(self, **kwargs):
164
        self.start_date = \
165
            datetime(self.start_date.year, self.start_date.month,
166
                self.start_date.day)
167
        if self.end_date:
168
            self.end_date = \
169
                datetime(self.end_date.year, self.end_date.month,
170
                    self.end_date.day)
171
        super(SessadHealthCareNotification, self).save(**kwargs)
172

    
173
class ProtectionStatus(NamedAbstractModel):
174

    
175
    class Meta:
176
        app_label = 'dossiers'
177
        verbose_name = u"Statut d'une mesure de protection"
178
        verbose_name_plural = u"Statuts d'une mesure de protection"
179

    
180
class ProtectionState(models.Model):
181

    
182
    class Meta:
183
        app_label = 'dossiers'
184
        verbose_name = u'Mesure de protection du dossier patient'
185
        verbose_name_plural = u'Mesure de protections du dossier patient'
186
        ordering = ['-start_date']
187

    
188
    patient = models.ForeignKey('dossiers.PatientRecord',
189
        verbose_name=u'Dossier patient')
190
    status = models.ForeignKey('dossiers.ProtectionStatus', verbose_name=u'Statut de protection')
191
    created = models.DateTimeField(u'Création', auto_now_add=True)
192
    start_date = models.DateTimeField()
193
    end_date = models.DateTimeField(blank=True, null=True)
194
    comment = models.TextField(max_length=3000, blank=True, null=True)
195

    
196
    def __unicode__(self):
197
        return self.status.name + ' ' + str(self.start_date)
198

    
199
class Status(NamedAbstractModel):
200

    
201
    class Meta:
202
        app_label = 'dossiers'
203
        verbose_name = u"Statut d'un état"
204
        verbose_name_plural = u"Statuts d'un état"
205

    
206
    type = models.CharField(max_length=80)
207
    services = models.ManyToManyField('ressources.Service')
208

    
209

    
210
class FileState(models.Model):
211

    
212
    class Meta:
213
        app_label = 'dossiers'
214
        verbose_name = u'Etat du dossier patient'
215
        verbose_name_plural = u'Etats du dossier patient'
216

    
217
    patient = models.ForeignKey('dossiers.PatientRecord',
218
        verbose_name=u'Dossier patient')
219
    status = models.ForeignKey('dossiers.Status', verbose_name=u'Statut')
220
    created = models.DateTimeField(u'Création', auto_now_add=True)
221
    date_selected = models.DateTimeField()
222
    author = \
223
        models.ForeignKey(User,
224
        verbose_name=u'Auteur')
225
    comment = models.TextField(max_length=3000, blank=True, null=True)
226
    previous_state = models.ForeignKey('FileState',
227
            on_delete=models.SET_NULL,
228
            verbose_name=u'Etat précédent',
229
            blank=True, null=True)
230

    
231
    def get_next_state(self):
232
        try:
233
            return FileState.objects.get(previous_state=self)
234
        except:
235
            return None
236

    
237
    def save(self, **kwargs):
238
        self.date_selected = \
239
                datetime(self.date_selected.year,
240
                        self.date_selected.month, self.date_selected.day)
241
        super(FileState, self).save(**kwargs)
242

    
243
    def __unicode__(self):
244
        return self.status.name + ' ' + str(self.date_selected)
245

    
246
    def delete(self, *args, **kwargs):
247
        next_state = self.get_next_state()
248
        if next_state and self.previous_state:
249
            next_state.previous_state = self.previous_state
250
            next_state.save()
251
        if self.patient.last_state == self:
252
            self.patient.last_state = self.previous_state
253
            self.patient.save()
254
        obj_id = self.id
255
        super(FileState, self).delete(*args, **kwargs)
256

    
257
class PatientAddress(models.Model):
258

    
259
    display_name = models.CharField(max_length=276,
260
            verbose_name=u'Adresse complète', editable=False)
261
    phone = PhoneNumberField(verbose_name=u"Téléphone", blank=True, null=True)
262
    fax = PhoneNumberField(verbose_name=u"Fax", blank=True, null=True)
263
    place_of_life = models.BooleanField(verbose_name=u"Lieu de vie")
264
    number = models.CharField(max_length=12,
265
            verbose_name=u"Numéro", blank=True, null=True)
266
    recipient = models.CharField(max_length=100,
267
            verbose_name=u"Destinataire", blank=True, null=True)
268
    street = models.CharField(max_length=100,
269
            verbose_name=u"Rue", blank=True, null=True)
270
    address_complement = models.CharField(max_length=100,
271
            blank=True, null=True,
272
            verbose_name=u"Complément d'adresse")
273
    zip_code = ZipCodeField(verbose_name=u"Code postal", blank=True, null=True)
274
    city = models.CharField(max_length=60,
275
            verbose_name=u"Ville", blank=True, null=True)
276
    comment = models.TextField(verbose_name=u"Commentaire",
277
            null=True, blank=True)
278

    
279
    def __unicode__(self):
280
        return self.display_name or u"Non renseigné"
281

    
282
    def save(self, **kwargs):
283
        self.display_name = ''
284
        if self.recipient:
285
            self.display_name += self.recipient + ' '
286
        if self.number:
287
            self.display_name += self.number + ' '
288
        if self.street:
289
            self.display_name += self.street + ' '
290
        if self.address_complement:
291
            self.display_name += self.address_complement + ' '
292
        if self.zip_code:
293
            self.display_name += self.zip_code + ' '
294
        if self.city:
295
            self.display_name += self.city + ' '
296
        super(PatientAddress, self).save(**kwargs)
297

    
298
class PatientContact(People):
299
    class Meta:
300
        verbose_name = u'Contact patient'
301
        verbose_name_plural = u'Contacts patient'
302

    
303
    mobile = PhoneNumberField(verbose_name=u"Téléphone mobile", blank=True, null=True)
304
    # carte vitale
305
    social_security_id = models.CharField(max_length=13, verbose_name=u"NIR",
306
            null=True, blank=True)
307
    birthdate = models.DateField(verbose_name=u"Date de naissance",
308
            null=True, blank=True)
309
    birthplace = models.CharField(max_length=100, verbose_name=u"Lieu de naissance",
310
            null=True, blank=True)
311
    twinning_rank = models.IntegerField(verbose_name=u"Rang (gémellité)", default=1,
312
            validators=[MinValueValidator(1)])
313
    thirdparty_payer = models.BooleanField(verbose_name=u'Tiers-payant',
314
            default=False)
315
    begin_rights = models.DateField(verbose_name=u"Début de droits",
316
            null=True, blank=True)
317
    end_rights = models.DateField(verbose_name=u"Fin de droits",
318
            null=True, blank=True)
319
    health_center = models.ForeignKey('ressources.HealthCenter',
320
            verbose_name=u"Centre d'assurance maladie",
321
            null=True, blank=True)
322
    other_health_center = models.CharField(verbose_name=u"Centre spécifique",
323
            max_length=4,
324
            null=True, blank=True)
325
    type_of_contract = models.CharField(max_length=2,
326
            verbose_name=u"Type de contrat spécifique",
327
            choices=TYPE_OF_CONTRACT_CHOICES,
328
            null=True, blank=True)
329
    management_code = models.ForeignKey('ressources.ManagementCode',
330
            verbose_name=u"Code de gestion",
331
            null=True, blank=True)
332
    job = models.ForeignKey('ressources.Job',
333
            related_name="job",
334
            verbose_name=u"Profession",
335
            null=True, blank=True, default=None)
336
    parente = models.ForeignKey('ressources.PatientRelatedLink',
337
            verbose_name=u"Lien avec le patient (Parenté)",
338
            null=True, blank=True, default=None)
339
    ame = models.BooleanField(verbose_name=u"AME", default=False)
340

    
341
    addresses = models.ManyToManyField('PatientAddress', verbose_name=u"Adresses")
342
    contact_comment = models.TextField(verbose_name=u"Commentaire",
343
            null=True, blank=True)
344

    
345
    old_contact_id = models.CharField(max_length=256,
346
            verbose_name=u'Ancien ID du contact', blank=True, null=True)
347

    
348
    def get_control_key(self):
349
        if self.social_security_id:
350
            nir = self.social_security_id
351
            try:
352
                # Corse dpt 2A et 2B
353
                minus = 0
354
                if nir[6] in ('A', 'a'):
355
                    nir = [c for c in nir]
356
                    nir[6] = '0'
357
                    nir = ''.join(nir)
358
                    minus = 1000000
359
                elif nir[6] in ('B', 'b'):
360
                    nir = [c for c in nir]
361
                    nir[6] = '0'
362
                    nir = ''.join(nir)
363
                    minus = 2000000
364
                nir = int(nir) - minus
365
                return (97 - (nir % 97))
366
            except Exception, e:
367
                logger.warning("%s" % str(e))
368
                return None
369
        return None
370

    
371

    
372
class PatientRecordManager(models.Manager):
373
    def for_service(self, service):
374
        return self.filter(service=service)
375

    
376
class PatientRecord(ServiceLinkedAbstractModel, PatientContact):
377
    objects = PatientRecordManager()
378

    
379
    class Meta:
380
        verbose_name = u'Dossier'
381
        verbose_name_plural = u'Dossiers'
382

    
383
    created = models.DateTimeField(u'création', auto_now_add=True)
384
    creator = \
385
        models.ForeignKey(User,
386
        verbose_name=u'Créateur dossier patient',
387
        editable=True)
388
    policyholder = models.ForeignKey('PatientContact',
389
            null=True, blank=True,
390
            verbose_name="Assuré", related_name="+",
391
            on_delete=models.SET_NULL)
392
    contacts = models.ManyToManyField('PatientContact',
393
            related_name='contact_of')
394
    nationality = models.CharField(verbose_name=u"Nationalité",
395
            max_length=70, null=True, blank=True)
396
    paper_id = models.CharField(max_length=6,
397
            verbose_name=u"N° dossier papier",
398
            null=True, blank=True)
399
    last_state = models.ForeignKey(FileState, related_name='+',
400
            null=True, on_delete=models.SET_NULL)
401
    comment = models.TextField(verbose_name=u"Commentaire",
402
            null=True, blank=True, default=None)
403
    pause = models.BooleanField(verbose_name=u"Pause facturation",
404
            default=False)
405
    pause_comment = models.TextField(verbose_name=u"Commentaire sur la pause facturation",
406
            null=True, blank=True, default=None)
407
    confidential = models.BooleanField(verbose_name=u"Confidentiel",
408
            default=False)
409
    socialisation_durations = models.ManyToManyField('ressources.SocialisationDuration',
410
            related_name='socialisation_duration_of')
411
    mdph_requests = models.ManyToManyField('ressources.MDPHRequest',
412
            related_name='mdph_requests_of')
413
    mdph_responses = models.ManyToManyField('ressources.MDPHResponse',
414
            related_name='mdph_responses_of')
415

    
416
    # Physiology and health data
417
    size = models.DecimalField(verbose_name=u"Taille (cm)", max_digits=5, decimal_places=1,
418
            null=True, blank=True, default=None)
419
    weight = models.IntegerField(verbose_name=u"Poids (g)",
420
            null=True, blank=True, default=None)
421
    pregnancy_term = models.IntegerField(verbose_name=u"Terme en semaines",
422
            null=True, blank=True, default=None)
423
    cranium_perimeter = models.DecimalField(verbose_name=u"Périmètre cranien", max_digits=5, decimal_places=2,
424
            null=True, blank=True, default=None)
425
    chest_perimeter = models.DecimalField(verbose_name=u"Périmètre thoracique", max_digits=5, decimal_places=2,
426
            null=True, blank=True, default=None)
427
    apgar_score_one = models.IntegerField(verbose_name=u"Test d'Apgar (1)",
428
            null=True, blank=True, default=None)
429
    apgar_score_two = models.IntegerField(verbose_name=u"Test d'Apgar (5)",
430
            null=True, blank=True, default=None)
431
    mises_1 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises1",
432
            verbose_name=u"Axe I : catégories cliniques",
433
            null=True, blank=True, default=None)
434
    mises_2 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises2",
435
            verbose_name=u"Axe II : facteurs organiques",
436
            null=True, blank=True, default=None)
437
    mises_3 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises3",
438
            verbose_name=u"Axe II : facteurs environnementaux",
439
            null=True, blank=True, default=None)
440
    deficiency_intellectual = models.IntegerField(max_length=1,
441
            verbose_name=u"Déficiences intellectuelles",
442
            choices=DEFICIENCY_CHOICES,
443
            default=0)
444
    deficiency_autism_and_other_ted = models.IntegerField(max_length=1,
445
            verbose_name=u"Autisme et autres TED",
446
            choices=DEFICIENCY_CHOICES,
447
            default=0)
448
    deficiency_mental_disorder = models.IntegerField(max_length=1,
449
            verbose_name=u"Troubles psychiques",
450
            choices=DEFICIENCY_CHOICES,
451
            default=0)
452
    deficiency_learning_disorder = models.IntegerField(max_length=1,
453
            verbose_name=u"Troubles du langage et des apprentissages",
454
            choices=DEFICIENCY_CHOICES,
455
            default=0)
456
    deficiency_auditory = models.IntegerField(max_length=1,
457
            verbose_name=u"Déficiences auditives",
458
            choices=DEFICIENCY_CHOICES,
459
            default=0)
460
    deficiency_visual = models.IntegerField(max_length=1,
461
            verbose_name=u"Déficiences visuelles",
462
            choices=DEFICIENCY_CHOICES,
463
            default=0)
464
    deficiency_motor = models.IntegerField(max_length=1,
465
            verbose_name=u"Déficiences motrices",
466
            choices=DEFICIENCY_CHOICES,
467
            default=0)
468
    deficiency_metabolic_disorder = models.IntegerField(max_length=1,
469
            verbose_name=u"Déficiences métaboliques",
470
            choices=DEFICIENCY_CHOICES,
471
            default=0)
472
    deficiency_brain_damage = models.IntegerField(max_length=1,
473
            verbose_name=u"Cérébro-lésions",
474
            choices=DEFICIENCY_CHOICES,
475
            default=0)
476
    deficiency_polyhandicap = models.BooleanField(verbose_name=u'Polyhandicap',
477
            default=False)
478
    deficiency_behavioral_disorder = models.IntegerField(max_length=1,
479
            verbose_name=u"Troubles de la conduite et du comportement",
480
            choices=DEFICIENCY_CHOICES,
481
            default=0)
482
    deficiency_in_diagnostic = models.BooleanField(verbose_name=u'En diagnostic',
483
            default=False)
484
    deficiency_other_disorder = models.IntegerField(max_length=1,
485
            verbose_name=u"Autres types de déficience",
486
            choices=DEFICIENCY_CHOICES,
487
            default=0)
488

    
489
    # Inscription motive
490
    analysemotive = models.ForeignKey('ressources.AnalyseMotive',
491
            verbose_name=u"Motif (analysé)",
492
            null=True, blank=True, default=None)
493
    familymotive = models.ForeignKey('ressources.FamilyMotive',
494
            verbose_name=u"Motif (famille)",
495
            null=True, blank=True, default=None)
496
    provenance = models.ForeignKey('ressources.Provenance',
497
            verbose_name=u"Conseilleur",
498
            null=True, blank=True, default=None)
499
    advicegiver = models.ForeignKey('ressources.AdviceGiver',
500
            verbose_name=u"Demandeur",
501
            null=True, blank=True, default=None)
502
    provenanceplace = models.ForeignKey('ressources.ProvenancePlace',
503
            verbose_name=u"Lieu de provenance",
504
            null=True, blank=True, default=None)
505

    
506
    # Out motive
507
    outmotive = models.ForeignKey('ressources.OutMotive',
508
            verbose_name=u"Motif de sortie",
509
            null=True, blank=True, default=None)
510
    outto = models.ForeignKey('ressources.OutTo',
511
            verbose_name=u"Orientation",
512
            null=True, blank=True, default=None)
513

    
514
    # Family
515
    sibship_place = models.IntegerField(verbose_name=u"Place dans la fratrie",
516
            null=True, blank=True, default=None)
517
    nb_children_family = models.IntegerField(verbose_name=u"Nombre d'enfants dans la fratrie",
518
            null=True, blank=True, default=None)
519
    parental_authority = models.ForeignKey('ressources.ParentalAuthorityType',
520
            verbose_name=u"Autorité parentale",
521
            null=True, blank=True, default=None)
522
    family_situation = models.ForeignKey('ressources.FamilySituationType',
523
            verbose_name=u"Situation familiale",
524
            null=True, blank=True, default=None)
525
    child_custody = models.ForeignKey('ressources.ParentalCustodyType',
526
            verbose_name=u"Garde parentale",
527
            null=True, blank=True, default=None)
528
    job_mother = models.ForeignKey('ressources.Job',
529
            related_name="job_mother",
530
            verbose_name=u"Profession de la mère",
531
            null=True, blank=True, default=None)
532
    job_father = models.ForeignKey('ressources.Job',
533
            related_name="job_father",
534
            verbose_name=u"Profession du père",
535
            null=True, blank=True, default=None)
536
    rm_mother = models.ForeignKey('ressources.MaritalStatusType',
537
            related_name="rm_mother",
538
            verbose_name=u"Régime matrimonial de la mère",
539
            null=True, blank=True, default=None)
540
    rm_father = models.ForeignKey('ressources.MaritalStatusType',
541
            related_name="rm_father",
542
            verbose_name=u"Régime matrimonial du père",
543
            null=True, blank=True, default=None)
544
    family_comment = models.TextField(verbose_name=u"Commentaire",
545
            null=True, blank=True, default=None)
546

    
547
    # Transport
548
    transporttype = models.ForeignKey('ressources.TransportType',
549
            verbose_name=u"Type de transport",
550
            null=True, blank=True, default=None)
551
    transportcompany = models.ForeignKey('ressources.TransportCompany',
552
            verbose_name=u"Compagnie de transport",
553
            null=True, blank=True, default=None)
554
    simple_appointment_transport = models.BooleanField(u'Afficher par défaut le transport sur les rendez-vous simples',
555
                                                       default=False)
556
    periodic_appointment_transport = models.BooleanField(u'Afficher par défaut le transport sur les rendez-vous réguliers',
557
                                                         default=False)
558

    
559
    # FollowUp
560
    coordinators = models.ManyToManyField('personnes.Worker',
561
            verbose_name=u"Coordinateurs",
562
            null=True, blank=True, default=None)
563
    externaldoctor = models.ForeignKey('personnes.ExternalTherapist',
564
            verbose_name=u"Médecin extérieur",
565
            null=True, blank=True, default=None)
566
    externalintervener = models.ForeignKey('personnes.ExternalWorker',
567
            verbose_name=u"Intervenant extérieur",
568
            null=True, blank=True, default=None)
569

    
570
    old_id = models.CharField(max_length=256,
571
            verbose_name=u'Ancien ID', blank=True, null=True)
572
    old_old_id = models.CharField(max_length=256,
573
            verbose_name=u'Ancien ancien ID', blank=True, null=True)
574

    
575
    def save(self, *args, **kwargs):
576
        if not getattr(self, 'service', None):
577
            raise Exception('The field service is mandatory.')
578
        super(PatientRecord, self).save(*args, **kwargs)
579

    
580
    def get_state(self):
581
        return self.last_state
582

    
583
    def get_initial_state(self):
584
        return self.filestate_set.order_by('date_selected')[0]
585

    
586
    def get_current_state(self):
587
        today = date.today()
588
        return self.get_state_at_day(today)
589

    
590
    def get_state_at_day(self, date):
591
        state = self.get_state()
592
        while(state):
593
            if datetime(state.date_selected.year,
594
                    state.date_selected.month, state.date_selected.day) <= \
595
                    datetime(date.year, date.month, date.day):
596
                return state
597
            state = state.previous_state
598
        return None
599

    
600
    def was_in_state_at_day(self, date, status_type):
601
        state_at_day = self.get_state_at_day(date)
602
        if state_at_day and state_at_day.status.type == status_type:
603
            return True
604
        return False
605

    
606
    def get_states_history(self):
607
        return self.filestate_set.order_by('date_selected')
608

    
609
    def get_states_history_with_duration(self):
610
        '''
611
        Return the state history with for each state its duration.
612
        If the last state is in the past, the duration is counted until today.
613
        If the last state is in the future, the duration is not set.
614
        '''
615
        history = self.get_states_history()
616
        history_with_duration = list()
617
        today = datetime.today()
618
        i = 0
619
        for state in history:
620
            history_with_duration.append([state, None])
621
            if i != 0:
622
                history_with_duration[i-1][1] = state.date_selected - history_with_duration[i-1][0].date_selected
623
            if i == len(history)-1 and state.date_selected <= today:
624
                history_with_duration[i][1] = today - history_with_duration[i][0].date_selected
625
            i += 1
626
        return history_with_duration
627

    
628
    def can_be_deleted(self):
629
        for act in self.act_set.all():
630
            if act.is_state('VALIDE'):
631
                return False
632
        return True
633

    
634
    def delete(self, *args, **kwargs):
635
        if self.can_be_deleted():
636
            obj_id = self.id
637
            super(PatientRecord, self).delete(*args, **kwargs)
638

    
639
    def get_ondisk_directory(self, service):
640
        if not settings.PATIENT_FILES_BASE_DIRECTORY:
641
            return None
642

    
643
        dirnames = []
644
        dirname = self.last_name.upper()
645
        dirnames.append(dirname)
646
        if self.first_name:
647
            dirname = '%s %s' % (dirname, self.first_name)
648
            dirnames.append(dirname)
649
        if self.paper_id:
650
            dirname = '%s %s' % (dirname, self.paper_id)
651
            dirnames.append(dirname)
652

    
653
        for i, dirname in enumerate(dirnames):
654
            fullpath = os.path.join(settings.PATIENT_FILES_BASE_DIRECTORY, service, dirname)
655
            try:
656
                next_fullpath = os.path.join(settings.PATIENT_FILES_BASE_DIRECTORY, service, dirnames[i+1])
657
            except IndexError:
658
                pass
659
            else:
660
                if os.path.exists(fullpath) and not os.path.exists(next_fullpath):
661
                    os.rename(fullpath, next_fullpath)
662
                continue
663
            if not os.path.exists(fullpath):
664
                os.makedirs(fullpath)
665
            for subdir in settings.PATIENT_SUBDIRECTORIES:
666
                subdir_fullpath = os.path.join(fullpath, subdir)
667
                if not os.path.exists(subdir_fullpath):
668
                    os.makedirs(subdir_fullpath)
669
        return fullpath
670

    
671
    def get_client_side_directory(self, service):
672
        directory = self.get_ondisk_directory(service)
673
        if not directory:
674
            return None
675
        if not settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY:
676
            return None
677
        return os.path.join(settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY,
678
                            directory[len(settings.PATIENT_FILES_BASE_DIRECTORY)+1:])
679

    
680
    def set_state(self, status, author, date_selected=None, comment=None):
681
        if not author:
682
            raise Exception('Missing author to set state')
683
        if not date_selected:
684
            date_selected = datetime.now()
685
        current_state = self.get_state()
686
        if not current_state:
687
            raise Exception('Invalid patient record. '
688
                'Missing current state.')
689
        if isinstance(date_selected, date):
690
            date_selected = datetime(year=date_selected.year,
691
                month=date_selected.month, day=date_selected.day)
692
        if date_selected < current_state.date_selected:
693
            raise Exception('You cannot set a state starting the %s that '
694
                'is before the previous state starting at day %s.' % \
695
                (str(date_selected), str(current_state.date_selected)))
696
        filestate = FileState.objects.create(patient=self, status=status,
697
            date_selected=date_selected, author=author, comment=comment,
698
            previous_state=current_state)
699
        self.last_state = filestate
700
        self.save()
701

    
702
    def change_day_selected_of_state(self, state, new_date):
703
        if state.previous_state:
704
            if new_date < state.previous_state.date_selected:
705
                raise Exception('You cannot set a state starting the %s '
706
                    'before the previous state starting at day %s.' % \
707
                    (str(new_date), str(state.previous_state.date_selected)))
708
        next_state = state.get_next_state()
709
        if next_state:
710
            if new_date > next_state.date_selected:
711
                raise Exception('You cannot set a state starting the %s '
712
                    'after the following state starting at day %s.' % \
713
                    (str(new_date), str(next_state.date_selected)))
714
        state.date_selected = new_date
715
        state.save()
716

    
717
    def remove_state(self, state):
718
        if state.patient.id != self.id:
719
            raise Exception('The state given is not about this patient '
720
                'record but about %s' % state.patient)
721
        next_state = state.get_next_state()
722
        if not next_state:
723
            self.remove_last_state()
724
        else:
725
            next_state.previous_state = state.previous_state
726
            next_state.save()
727
            state.delete()
728

    
729
    def remove_last_state(self):
730
        try:
731
            self.get_state().delete()
732
        except:
733
            pass
734

    
735
    def get_protection_state_at_date(self, date):
736
        try:
737
            return self.protectionstate_set.exclude(end_date__lt=date). \
738
                exclude(start_date__gt=date).latest('start_date')
739
        except:
740
            return None
741

    
742
    def get_next_rdv(self):
743
        from views_utils import get_next_rdv
744
        return get_next_rdv(self)
745

    
746
    # START Specific to sessad healthcare
747
    def get_last_notification(self):
748
        return SessadHealthCareNotification.objects.filter(patient=self, ).\
749
            latest('end_date')
750

    
751
    def days_before_notification_expiration(self):
752
        today = datetime.today()
753
        notification = self.get_last_notification(self)
754
        if not notification:
755
            return 0
756
        if notification.end_date < today:
757
            return 0
758
        else:
759
            return notification.end_date - today
760
    # END Specific to sessad healthcare
761

    
762
    # START Specific to cmpp healthcare
763
    def create_diag_healthcare(self, modifier):
764
        """
765
            Gestion de l'inscription automatique.
766

    
767
            Si un premier acte est validé alors une prise en charge
768
            diagnostique est ajoutée. Cela fera basculer le dossier dans l'état
769
            en diagnostic.
770

    
771
            A voir si auto ou manuel :
772
            Si ce n'est pas le premier acte validé mais que l'acte précédement
773
            facturé a plus d'un an, on peut créer une prise en charge
774
            diagnostique. Même s'il y a une prise en charge de traitement
775
            expirée depuis moins d'un an donc renouvelable.
776

    
777
        """
778
        acts = Act.objects.filter(validation_locked=False,
779
            patient__service=self.service)
780
        days_not_locked = sorted(set(acts.values_list('date', flat=True)))
781
        acts = self.act_set.filter(validation_locked=True,
782
            valide=True, is_lost=False, is_billed=False)
783
        acts = acts.exclude(date__in=days_not_locked)
784
        acts = acts.order_by('date')
785
        pause_query = Q(pause=True)
786
        billable_query = Q(act_type__billable=True, switch_billable=False) | \
787
                Q(act_type__billable=False, switch_billable=True)
788
        billable_acts = acts.filter(~pause_query & billable_query)
789

    
790
        if not CmppHealthCareDiagnostic.objects.filter(patient=self).exists() \
791
                and billable_acts:
792
            # Pas de prise en charge, on recherche l'acte facturable le plus
793
            # ancien, on crée une pc diag à la même date.
794
            CmppHealthCareDiagnostic(patient=self, author=modifier,
795
                start_date=billable_acts[0].date).save()
796
        else:
797
            # On recherche l'acte facturable non facturé le plus ancien après
798
            # le dernier acte facturé et on regarde s'il a plus d'un an
799
            try:
800
                last_billed_act = self.act_set.filter(is_billed=True).\
801
                    latest('date')
802
                if last_billed_act and billable_acts:
803
                    billable_acts = billable_acts.filter(date__gte=last_billed_act.date)
804
                    if billable_acts and (billable_acts[0].date - last_billed_act.date).days >= 365:
805
                        return True
806
                    return False
807
            except:
808
                pass
809
        return False
810

    
811
    def automated_switch_state(self, modifier):
812
        def state_switcher(diag, act):
813
            if diag and (self.last_state.status.type == "ACCUEIL" or
814
                    self.last_state.status.type == "TRAITEMENT"):
815
                status = Status.objects.get(type="DIAGNOSTIC",
816
                    services__name='CMPP')
817
                try:
818
                    self.set_state(status, modifier, date_selected=act.date)
819
                except:
820
                    pass
821
            elif not diag and (self.last_state.status.type == "ACCUEIL" or
822
                    self.last_state.status.type == "DIAGNOSTIC"):
823
                status = Status.objects.get(type="TRAITEMENT",
824
                    services__name='CMPP')
825
                try:
826
                    self.set_state(status, modifier, date_selected=act.date)
827
                except:
828
                    pass
829
        # Only for CMPP and open files
830
        if not self.service.name == 'CMPP' or \
831
                self.last_state.status.type == "CLOS":
832
            return
833
        # Nothing to do if no act after the last state date
834
        last_acts = self.act_set.filter(date__gt=self.last_state.date_selected)
835
        if not last_acts:
836
            return
837
        # If the last act is billed, look at the healthcare type
838
        last_act = last_acts.latest('date')
839
        if last_act.is_billed:
840
            if not last_act.healthcare:
841
                # Billed but no healthcare, coming from imported billed acts
842
                return
843
            diag = False
844
            if hasattr(last_act.healthcare, 'cmpphealthcarediagnostic'):
845
                diag = True
846
            return state_switcher(diag, last_act)
847
        # Last act not billed, let's look if it is billable
848
        from calebasse.facturation import list_acts
849
        (acts_not_locked, days_not_locked, acts_not_valide,
850
        acts_not_billable, acts_pause, acts_per_hc, acts_losts) = \
851
            list_acts.list_acts_for_billing_CMPP_per_patient(self,
852
                datetime.today(), self.service)
853
        last_hc = None
854
        last_act_hc = None
855
        for hc, acts in acts_per_hc.iteritems():
856
            if len(acts) and (not last_act_hc or
857
                    acts[-1].date > last_act_hc.date):
858
                last_hc = hc
859
                last_act_hc = acts[-1]
860
        # There is a billable act after the last state so either it is diag
861
        # or it is treament
862
        if last_act_hc and last_hc and \
863
                last_act_hc.date > self.last_state.date_selected.date():
864
            if hasattr(last_hc, 'cmpphealthcarediagnostic'):
865
                state_switcher(True, last_act_hc)
866
            else:
867
                state_switcher(False, last_act_hc)
868

    
869
    def get_healthcare_status(self):
870
        today = date.today()
871
        current_hc_trait = None
872
        try:
873
            current_hc_trait = CmppHealthCareTreatment.objects.filter(
874
                patient=self, start_date__lte=today, end_date__gte=today
875
            ).latest('start_date')
876
        except:
877
            pass
878
        if not current_hc_trait:
879
            current_hc_diag = None
880
            try:
881
                current_hc_diag = CmppHealthCareDiagnostic.objects.filter(patient=self, start_date__lte=today).latest('start_date')
882
            except:
883
                pass
884
            if current_hc_diag and current_hc_diag.get_act_number() > len(current_hc_diag.act_set.all()):
885

    
886
                #Plus simple et a changer dans la facturation, s'il y une pc de traitemant avec une start date > a la pc diag alors cette pc de diag est finie
887
                # Non parce que je peux ajouter une pc de traitement alors que je veux encore facturer sur la diag precedente.
888
                # Donc si j'ai un acte facturer en traitement alors la diag ne fonctionne plus.
889
                # Dans le fonctionnement normal, si j'ai encore une diag dispo et que je veux fact duirectement en trait, je reduit le nombre d'acte pris en charge.
890
                lasts_billed = Act.objects.filter(patient=self, is_billed = True, healthcare__isnull=False).order_by('-date')
891
                last_hc_date = None
892
                if lasts_billed:
893
                    last_hc_date = lasts_billed[0].healthcare.start_date
894
                if not last_hc_date or last_hc_date <= current_hc_diag.start_date:
895
                    # Prise en charge disponible
896
                    return (0, len(current_hc_diag.act_set.all()), current_hc_diag.get_act_number())
897
            last_hc_trait = None
898
            try:
899
                last_hc_trait = CmppHealthCareTreatment.objects.filter(patient=self).latest('start_date')
900
            except:
901
                pass
902
            if not last_hc_trait:
903
                if not current_hc_diag:
904
                    # Aucune PC
905
                    return (1, None)
906
                else:
907
                    # PC diag full, demander PC trait
908
                    return (2, current_hc_diag.get_act_number())
909
            if last_hc_trait.end_date < today:
910
                # Expirée
911
                #Test if rediagable
912
                return (3, last_hc_trait.end_date)
913
            if last_hc_trait.start_date > today:
914
                # N'a pas encore pris effet
915
                return (4, last_hc_trait.start_date)
916
            return (-1,)
917
        if current_hc_trait.get_act_number() > len(current_hc_trait.act_set.all()):
918
            # Pris en charge disponible
919
            return (5, len(current_hc_trait.act_set.all()), current_hc_trait.get_act_number())
920
        # Prise en charge au quota
921
        if not current_hc_trait.is_extended():
922
            # Peut être prolongée
923
            return (6, current_hc_trait.get_act_number())
924
        # Prise en charge saturée
925
        return (7, current_hc_trait.get_act_number(), current_hc_trait.end_date)
926
    # END Specific to cmpp healthcare
927

    
928

    
929
    @property
930
    def entry_date(self):
931
        d = self.filestate_set.filter(
932
                Q(status__type='DIAGNOSTIC') |
933
                Q(status__type='TRAITEMENT') |
934
                Q(status__type='SUIVI')). \
935
                        aggregate(Min('date_selected'))['date_selected__min']
936
        return d and d.date()
937

    
938

    
939
    @property
940
    def exit_date(self):
941
        if self.last_state.status.type != 'CLOS':
942
            return None
943
        d = self.filestate_set.filter(status__type='CLOS'). \
944
                    aggregate(Max('date_selected'))['date_selected__max']
945
        return d and d.date()
946

    
947
    @property
948
    def care_duration(self):
949
        # Duration between the first act present and the closing date.
950
        # If no closing date, end_date is the date of tha last act
951
        first_act_date = None
952
        try:
953
            first_act_date = self.act_set.filter(valide=True).order_by('date')[0].date
954
        except:
955
            return 0
956
        exit_date = self.exit_date
957
        if not exit_date:
958
            exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
959
        return (exit_date - first_act_date).days
960

    
961
    @property
962
    def care_duration_since_last_contact_or_first_act(self):
963
        # Duration between the first act present and the closing date.
964
        # If no closing date, end_date is the date of the last act
965
        contacts = FileState.objects.filter(patient=self, status__type='ACCUEIL').order_by('date_selected')
966
        last_contact = None
967
        first_act_after_last_contact = None
968
        if len(contacts) == 1:
969
            last_contact = contacts[0]
970
        elif len(contacts) > 1:
971
            last_contact = contacts[len(contacts)-1]
972
        if last_contact:
973
            # inscription act
974
            first_acts_after_last_contact = Act.objects.filter(patient=self, date__gte=last_contact.date_selected, valide=True).order_by('date')
975
            if first_acts_after_last_contact:
976
                first_act_after_last_contact = first_acts_after_last_contact[0]
977
        if not contacts:
978
            return self.care_duration
979
        if not first_act_after_last_contact:
980
            return 0
981
        exit_date = self.exit_date
982
        if not exit_date or exit_date < first_act_after_last_contact.date:
983
            exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
984
        return (exit_date - first_act_after_last_contact.date).days
985

    
986
    def care_duration_before_close_state(self, end_date=None):
987
        if not end_date:
988
            end_date = datetime.now()
989
        last_close = None
990
        try:
991
            last_close = FileState.objects.filter(status__type='CLOS',
992
                    patient=self, date_selected__lt=end_date).order_by('-date_selected')[0]
993
        except:
994
            pass
995
        first_act = None
996
        if last_close:
997
            try:
998
                first_act = Act.objects.filter(patient=self, valide=True, date__lte=end_date, date__gt=last_close.date_selected).order_by('date')[0]
999
            except:
1000
                return 0
1001
        else:
1002
            try:
1003
                first_act = Act.objects.filter(patient=self, valide=True, date__lte=end_date).order_by('date')[0]
1004
            except:
1005
                return 0
1006
        return (end_date.date() - first_act.date).days + 1
1007

    
1008
def create_patient(first_name, last_name, service, creator,
1009
        date_selected=None):
1010
    logger.debug('create_patient: creation for patient %s %s in service %s '
1011
        'by %s' % (first_name, last_name, service, creator))
1012
    if not (first_name and last_name and service and creator):
1013
        raise Exception('Missing parameter to create a patient record.')
1014
    status = Status.objects.filter(type="ACCUEIL").filter(services=service)
1015
    if not status:
1016
        raise Exception('%s has no ACCEUIL status' % service.name)
1017
    patient = PatientRecord.objects.create(first_name=first_name,
1018
            last_name=last_name, service=service,
1019
            creator=creator)
1020
    fs = FileState(status=status[0], author=creator, previous_state=None)
1021
    if not date_selected:
1022
        date_selected = patient.created
1023
    fs.patient = patient
1024
    fs.date_selected = date_selected
1025
    fs.save()
1026
    patient.last_state = fs
1027
    patient.save()
1028
    patient.policyholder = patient.patientcontact
1029
    patient.save()
1030
    return patient
(5-5/12)