Project

General

Profile

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

calebasse / calebasse / dossiers / models.py @ 017f4158

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
from ..utils import get_service_setting
25

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

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

    
35

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

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

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

    
51

    
52
class HealthCare(models.Model):
53

    
54
    class Meta:
55
        app_label = 'dossiers'
56

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

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

    
75

    
76
class CmppHealthCareDiagnostic(HealthCare):
77

    
78
    class Meta:
79
        app_label = 'dossiers'
80

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

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

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

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

    
101

    
102
class CmppHealthCareTreatment(HealthCare):
103

    
104
    class Meta:
105
        app_label = 'dossiers'
106

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

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

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

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

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

    
141
    def del_prolongation(self):
142
        pass
143

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

    
155

    
156
class SessadHealthCareNotification(HealthCare):
157

    
158
    class Meta:
159
        app_label = 'dossiers'
160

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

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

    
174
class ProtectionStatus(NamedAbstractModel):
175

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

    
181
class ProtectionState(models.Model):
182

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

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

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

    
200
class Status(NamedAbstractModel):
201

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

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

    
210

    
211
class FileState(models.Model):
212

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

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

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

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

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

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

    
258
class PatientAddress(models.Model):
259

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

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

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

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

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

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

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

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

    
373
    def age(self, age_format=None):
374
        if not self.birthdate:
375
            return 'inconnu'
376

    
377
        if not age_format:
378
            age_format = get_service_setting('age_format')
379

    
380
        now = datetime.today().date()
381
        age = relativedelta(now, self.birthdate)
382

    
383
        # by default we return the number of months for children < 2 years, but
384
        # there's a service setting to have it always displayed that way.
385
        months = age.years * 12 + age.months
386
        if months == 0:
387
            components = []
388
        elif age.years < 2 or age_format == 'months_only':
389
            components = ['%s mois' % months]
390
        else:
391
            components = ['%s ans' % age.years]
392
            if age.months:
393
                components.append('%s mois' % age.months)
394

    
395
        # under three months, we also display the number of days
396
        if months < 3:
397
            if age.days == 1:
398
                components.append("%s jour" % age.days)
399
            elif age.days > 1:
400
                components.append('%s jours' % age.days)
401

    
402
        return ' et '.join(components)
403

    
404

    
405
class PatientRecordManager(models.Manager):
406
    def for_service(self, service):
407
        return self.filter(service=service)
408

    
409
class PatientRecord(ServiceLinkedAbstractModel, PatientContact):
410
    objects = PatientRecordManager()
411

    
412
    class Meta:
413
        verbose_name = u'Dossier'
414
        verbose_name_plural = u'Dossiers'
415

    
416
    created = models.DateTimeField(u'création', auto_now_add=True)
417
    creator = \
418
        models.ForeignKey(User,
419
        verbose_name=u'Créateur dossier patient',
420
        editable=True)
421
    policyholder = models.ForeignKey('PatientContact',
422
            null=True, blank=True,
423
            verbose_name="Assuré", related_name="+",
424
            on_delete=models.SET_NULL)
425
    contacts = models.ManyToManyField('PatientContact',
426
            related_name='contact_of')
427
    nationality = models.CharField(verbose_name=u"Nationalité",
428
            max_length=70, null=True, blank=True)
429
    paper_id = models.CharField(max_length=6,
430
            verbose_name=u"N° dossier papier",
431
            null=True, blank=True)
432
    last_state = models.ForeignKey(FileState, related_name='+',
433
            null=True, on_delete=models.SET_NULL)
434
    comment = models.TextField(verbose_name=u"Commentaire",
435
            null=True, blank=True, default=None)
436
    pause = models.BooleanField(verbose_name=u"Pause facturation",
437
            default=False)
438
    pause_comment = models.TextField(verbose_name=u"Commentaire sur la pause facturation",
439
            null=True, blank=True, default=None)
440
    confidential = models.BooleanField(verbose_name=u"Confidentiel",
441
            default=False)
442
    socialisation_durations = models.ManyToManyField('ressources.SocialisationDuration',
443
            related_name='socialisation_duration_of')
444
    mdph_requests = models.ManyToManyField('ressources.MDPHRequest',
445
            related_name='mdph_requests_of')
446
    mdph_responses = models.ManyToManyField('ressources.MDPHResponse',
447
            related_name='mdph_responses_of')
448
    addresses_contacts_comment = models.TextField(verbose_name=u"Commentaire sur les adresses et contacts",
449
            null=True, blank=True, default=None)
450

    
451
    # Physiology and health data
452
    size = models.DecimalField(verbose_name=u"Taille (cm)", max_digits=5, decimal_places=1,
453
            null=True, blank=True, default=None)
454
    weight = models.IntegerField(verbose_name=u"Poids (g)",
455
            null=True, blank=True, default=None)
456
    pregnancy_term = models.IntegerField(verbose_name=u"Terme en semaines",
457
            null=True, blank=True, default=None)
458
    cranium_perimeter = models.DecimalField(verbose_name=u"Périmètre cranien", max_digits=5, decimal_places=2,
459
            null=True, blank=True, default=None)
460
    chest_perimeter = models.DecimalField(verbose_name=u"Périmètre thoracique", max_digits=5, decimal_places=2,
461
            null=True, blank=True, default=None)
462
    apgar_score_one = models.IntegerField(verbose_name=u"Test d'Apgar (1)",
463
            null=True, blank=True, default=None)
464
    apgar_score_two = models.IntegerField(verbose_name=u"Test d'Apgar (5)",
465
            null=True, blank=True, default=None)
466
    mises_1 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises1",
467
            verbose_name=u"Axe I : catégories cliniques",
468
            null=True, blank=True, default=None)
469
    mises_2 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises2",
470
            verbose_name=u"Axe II : facteurs organiques",
471
            null=True, blank=True, default=None)
472
    mises_3 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises3",
473
            verbose_name=u"Axe II : facteurs environnementaux",
474
            null=True, blank=True, default=None)
475
    deficiency_intellectual = models.IntegerField(max_length=1,
476
            verbose_name=u"Déficiences intellectuelles",
477
            choices=DEFICIENCY_CHOICES,
478
            default=0)
479
    deficiency_autism_and_other_ted = models.IntegerField(max_length=1,
480
            verbose_name=u"Autisme et autres TED",
481
            choices=DEFICIENCY_CHOICES,
482
            default=0)
483
    deficiency_mental_disorder = models.IntegerField(max_length=1,
484
            verbose_name=u"Troubles psychiques",
485
            choices=DEFICIENCY_CHOICES,
486
            default=0)
487
    deficiency_learning_disorder = models.IntegerField(max_length=1,
488
            verbose_name=u"Troubles du langage et des apprentissages",
489
            choices=DEFICIENCY_CHOICES,
490
            default=0)
491
    deficiency_auditory = models.IntegerField(max_length=1,
492
            verbose_name=u"Déficiences auditives",
493
            choices=DEFICIENCY_CHOICES,
494
            default=0)
495
    deficiency_visual = models.IntegerField(max_length=1,
496
            verbose_name=u"Déficiences visuelles",
497
            choices=DEFICIENCY_CHOICES,
498
            default=0)
499
    deficiency_motor = models.IntegerField(max_length=1,
500
            verbose_name=u"Déficiences motrices",
501
            choices=DEFICIENCY_CHOICES,
502
            default=0)
503
    deficiency_metabolic_disorder = models.IntegerField(max_length=1,
504
            verbose_name=u"Déficiences métaboliques",
505
            choices=DEFICIENCY_CHOICES,
506
            default=0)
507
    deficiency_brain_damage = models.IntegerField(max_length=1,
508
            verbose_name=u"Cérébro-lésions",
509
            choices=DEFICIENCY_CHOICES,
510
            default=0)
511
    deficiency_polyhandicap = models.BooleanField(verbose_name=u'Polyhandicap',
512
            default=False)
513
    deficiency_behavioral_disorder = models.IntegerField(max_length=1,
514
            verbose_name=u"Troubles de la conduite et du comportement",
515
            choices=DEFICIENCY_CHOICES,
516
            default=0)
517
    deficiency_in_diagnostic = models.BooleanField(verbose_name=u'En diagnostic',
518
            default=False)
519
    deficiency_other_disorder = models.IntegerField(max_length=1,
520
            verbose_name=u"Autres types de déficience",
521
            choices=DEFICIENCY_CHOICES,
522
            default=0)
523

    
524
    # Inscription motive
525
    analysemotive = models.ForeignKey('ressources.AnalyseMotive',
526
            verbose_name=u"Motif (analysé)",
527
            null=True, blank=True, default=None)
528
    familymotive = models.ForeignKey('ressources.FamilyMotive',
529
            verbose_name=u"Motif (famille)",
530
            null=True, blank=True, default=None)
531
    provenance = models.ForeignKey('ressources.Provenance',
532
            verbose_name=u"Conseilleur",
533
            null=True, blank=True, default=None)
534
    advicegiver = models.ForeignKey('ressources.AdviceGiver',
535
            verbose_name=u"Demandeur",
536
            null=True, blank=True, default=None)
537
    provenanceplace = models.ForeignKey('ressources.ProvenancePlace',
538
            verbose_name=u"Lieu de provenance",
539
            null=True, blank=True, default=None)
540

    
541
    # Out motive
542
    outmotive = models.ForeignKey('ressources.OutMotive',
543
            verbose_name=u"Motif de sortie",
544
            null=True, blank=True, default=None)
545
    outto = models.ForeignKey('ressources.OutTo',
546
            verbose_name=u"Orientation",
547
            null=True, blank=True, default=None)
548

    
549
    # Family
550
    sibship_place = models.IntegerField(verbose_name=u"Place dans la fratrie",
551
            null=True, blank=True, default=None)
552
    nb_children_family = models.IntegerField(verbose_name=u"Nombre d'enfants dans la fratrie",
553
            null=True, blank=True, default=None)
554
    parental_authority = models.ForeignKey('ressources.ParentalAuthorityType',
555
            verbose_name=u"Autorité parentale",
556
            null=True, blank=True, default=None)
557
    family_situation = models.ForeignKey('ressources.FamilySituationType',
558
            verbose_name=u"Situation familiale",
559
            null=True, blank=True, default=None)
560
    child_custody = models.ForeignKey('ressources.ParentalCustodyType',
561
            verbose_name=u"Garde parentale",
562
            null=True, blank=True, default=None)
563
    job_mother = models.ForeignKey('ressources.Job',
564
            related_name="job_mother",
565
            verbose_name=u"Profession de la mère",
566
            null=True, blank=True, default=None)
567
    job_father = models.ForeignKey('ressources.Job',
568
            related_name="job_father",
569
            verbose_name=u"Profession du père",
570
            null=True, blank=True, default=None)
571
    rm_mother = models.ForeignKey('ressources.MaritalStatusType',
572
            related_name="rm_mother",
573
            verbose_name=u"Régime matrimonial de la mère",
574
            null=True, blank=True, default=None)
575
    rm_father = models.ForeignKey('ressources.MaritalStatusType',
576
            related_name="rm_father",
577
            verbose_name=u"Régime matrimonial du père",
578
            null=True, blank=True, default=None)
579
    family_comment = models.TextField(verbose_name=u"Commentaire",
580
            null=True, blank=True, default=None)
581

    
582
    # Transport
583
    transporttype = models.ForeignKey('ressources.TransportType',
584
            verbose_name=u"Type de transport",
585
            null=True, blank=True, default=None)
586
    transportcompany = models.ForeignKey('ressources.TransportCompany',
587
            verbose_name=u"Compagnie de transport",
588
            null=True, blank=True, default=None)
589
    simple_appointment_transport = models.BooleanField(u'Afficher par défaut le transport sur les rendez-vous simples',
590
                                                       default=False)
591
    periodic_appointment_transport = models.BooleanField(u'Afficher par défaut le transport sur les rendez-vous réguliers',
592
                                                         default=False)
593

    
594
    # FollowUp
595
    coordinators = models.ManyToManyField('personnes.Worker',
596
            verbose_name=u"Coordinateurs",
597
            null=True, blank=True, default=None)
598
    externaldoctor = models.ForeignKey('personnes.ExternalTherapist',
599
            verbose_name=u"Médecin extérieur",
600
            null=True, blank=True, default=None)
601
    externalintervener = models.ForeignKey('personnes.ExternalWorker',
602
            verbose_name=u"Intervenant extérieur",
603
            null=True, blank=True, default=None)
604

    
605
    old_id = models.CharField(max_length=256,
606
            verbose_name=u'Ancien ID', blank=True, null=True)
607
    old_old_id = models.CharField(max_length=256,
608
            verbose_name=u'Ancien ancien ID', blank=True, null=True)
609

    
610
    def save(self, *args, **kwargs):
611
        if not getattr(self, 'service', None):
612
            raise Exception('The field service is mandatory.')
613
        super(PatientRecord, self).save(*args, **kwargs)
614

    
615
    def get_state(self):
616
        return self.last_state
617

    
618
    def get_initial_state(self):
619
        return self.filestate_set.order_by('date_selected')[0]
620

    
621
    def get_current_state(self):
622
        today = date.today()
623
        return self.get_state_at_day(today)
624

    
625
    def get_state_at_day(self, date):
626
        state = self.get_state()
627
        while(state):
628
            if datetime(state.date_selected.year,
629
                    state.date_selected.month, state.date_selected.day) <= \
630
                    datetime(date.year, date.month, date.day):
631
                return state
632
            state = state.previous_state
633
        return None
634

    
635
    def was_in_state_at_day(self, date, status_type):
636
        state_at_day = self.get_state_at_day(date)
637
        if state_at_day and state_at_day.status.type == status_type:
638
            return True
639
        return False
640

    
641
    def get_states_history(self):
642
        return self.filestate_set.order_by('date_selected')
643

    
644
    def get_states_history_with_duration(self):
645
        '''
646
        Return the state history with for each state its duration.
647
        If the last state is in the past, the duration is counted until today.
648
        If the last state is in the future, the duration is not set.
649
        '''
650
        history = self.get_states_history()
651
        history_with_duration = list()
652
        today = datetime.today()
653
        i = 0
654
        for state in history:
655
            history_with_duration.append([state, None])
656
            if i != 0:
657
                history_with_duration[i-1][1] = state.date_selected - history_with_duration[i-1][0].date_selected
658
            if i == len(history)-1 and state.date_selected <= today:
659
                history_with_duration[i][1] = today - history_with_duration[i][0].date_selected
660
            i += 1
661
        return history_with_duration
662

    
663
    def can_be_deleted(self):
664
        for act in self.act_set.all():
665
            if act.is_state('VALIDE'):
666
                return False
667
        return True
668

    
669
    def delete(self, *args, **kwargs):
670
        if self.can_be_deleted():
671
            obj_id = self.id
672
            super(PatientRecord, self).delete(*args, **kwargs)
673

    
674
    def get_ondisk_directory(self, service):
675
        if not settings.PATIENT_FILES_BASE_DIRECTORY:
676
            return None
677

    
678
        dirnames = []
679
        dirname = self.last_name.upper()
680
        dirnames.append(dirname)
681
        if self.first_name:
682
            dirname = '%s %s' % (dirname, self.first_name)
683
            dirnames.append(dirname)
684
        if self.paper_id:
685
            dirname = '%s %s' % (dirname, self.paper_id)
686
            dirnames.append(dirname)
687

    
688
        for i, dirname in enumerate(dirnames):
689
            fullpath = os.path.join(settings.PATIENT_FILES_BASE_DIRECTORY, service, dirname)
690
            try:
691
                next_fullpath = os.path.join(settings.PATIENT_FILES_BASE_DIRECTORY, service, dirnames[i+1])
692
            except IndexError:
693
                pass
694
            else:
695
                if os.path.exists(fullpath) and not os.path.exists(next_fullpath):
696
                    os.rename(fullpath, next_fullpath)
697
                continue
698
            if not os.path.exists(fullpath):
699
                os.makedirs(fullpath)
700
            for subdir in settings.PATIENT_SUBDIRECTORIES:
701
                subdir_fullpath = os.path.join(fullpath, subdir)
702
                if not os.path.exists(subdir_fullpath):
703
                    os.makedirs(subdir_fullpath)
704
        return fullpath
705

    
706
    def get_client_side_directory(self, service):
707
        directory = self.get_ondisk_directory(service)
708
        if not directory:
709
            return None
710
        if not settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY:
711
            return None
712
        return os.path.join(settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY,
713
                            directory[len(settings.PATIENT_FILES_BASE_DIRECTORY)+1:])
714

    
715
    def set_state(self, status, author, date_selected=None, comment=None):
716
        if not author:
717
            raise Exception('Missing author to set state')
718
        if not date_selected:
719
            date_selected = datetime.now()
720
        current_state = self.get_state()
721
        if not current_state:
722
            raise Exception('Invalid patient record. '
723
                'Missing current state.')
724
        if isinstance(date_selected, date):
725
            date_selected = datetime(year=date_selected.year,
726
                month=date_selected.month, day=date_selected.day)
727
        if date_selected < current_state.date_selected:
728
            raise Exception('You cannot set a state starting the %s that '
729
                'is before the previous state starting at day %s.' % \
730
                (str(date_selected), str(current_state.date_selected)))
731
        filestate = FileState.objects.create(patient=self, status=status,
732
            date_selected=date_selected, author=author, comment=comment,
733
            previous_state=current_state)
734
        self.last_state = filestate
735
        self.save()
736

    
737
    def change_day_selected_of_state(self, state, new_date):
738
        if state.previous_state:
739
            if new_date < state.previous_state.date_selected:
740
                raise Exception('You cannot set a state starting the %s '
741
                    'before the previous state starting at day %s.' % \
742
                    (str(new_date), str(state.previous_state.date_selected)))
743
        next_state = state.get_next_state()
744
        if next_state:
745
            if new_date > next_state.date_selected:
746
                raise Exception('You cannot set a state starting the %s '
747
                    'after the following state starting at day %s.' % \
748
                    (str(new_date), str(next_state.date_selected)))
749
        state.date_selected = new_date
750
        state.save()
751

    
752
    def remove_state(self, state):
753
        if state.patient.id != self.id:
754
            raise Exception('The state given is not about this patient '
755
                'record but about %s' % state.patient)
756
        next_state = state.get_next_state()
757
        if not next_state:
758
            self.remove_last_state()
759
        else:
760
            next_state.previous_state = state.previous_state
761
            next_state.save()
762
            state.delete()
763

    
764
    def remove_last_state(self):
765
        try:
766
            self.get_state().delete()
767
        except:
768
            pass
769

    
770
    def get_protection_state_at_date(self, date):
771
        try:
772
            return self.protectionstate_set.exclude(end_date__lt=date). \
773
                exclude(start_date__gt=date).latest('start_date')
774
        except:
775
            return None
776

    
777
    def get_next_rdv(self):
778
        from views_utils import get_next_rdv
779
        return get_next_rdv(self)
780

    
781
    # START Specific to sessad healthcare
782
    def get_last_notification(self):
783
        return SessadHealthCareNotification.objects.filter(patient=self, ).\
784
            latest('end_date')
785

    
786
    def days_before_notification_expiration(self):
787
        today = datetime.today()
788
        notification = self.get_last_notification(self)
789
        if not notification:
790
            return 0
791
        if notification.end_date < today:
792
            return 0
793
        else:
794
            return notification.end_date - today
795
    # END Specific to sessad healthcare
796

    
797
    # START Specific to cmpp healthcare
798
    def create_diag_healthcare(self, modifier):
799
        """
800
            Gestion de l'inscription automatique.
801

    
802
            Si un premier acte est validé alors une prise en charge
803
            diagnostique est ajoutée. Cela fera basculer le dossier dans l'état
804
            en diagnostic.
805

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

    
812
        """
813
        acts = Act.objects.filter(validation_locked=False,
814
            patient__service=self.service)
815
        days_not_locked = sorted(set(acts.values_list('date', flat=True)))
816
        acts = self.act_set.filter(validation_locked=True,
817
            valide=True, is_lost=False, is_billed=False)
818
        acts = acts.exclude(date__in=days_not_locked)
819
        acts = acts.order_by('date')
820
        pause_query = Q(pause=True)
821
        billable_query = Q(act_type__billable=True, switch_billable=False) | \
822
                Q(act_type__billable=False, switch_billable=True)
823
        billable_acts = acts.filter(~pause_query & billable_query)
824

    
825
        if not CmppHealthCareDiagnostic.objects.filter(patient=self).exists() \
826
                and billable_acts:
827
            # Pas de prise en charge, on recherche l'acte facturable le plus
828
            # ancien, on crée une pc diag à la même date.
829
            CmppHealthCareDiagnostic(patient=self, author=modifier,
830
                start_date=billable_acts[0].date).save()
831
        else:
832
            # On recherche l'acte facturable non facturé le plus ancien après
833
            # le dernier acte facturé et on regarde s'il a plus d'un an
834
            try:
835
                last_billed_act = self.act_set.filter(is_billed=True).\
836
                    latest('date')
837
                if last_billed_act and billable_acts:
838
                    billable_acts = billable_acts.filter(date__gte=last_billed_act.date)
839
                    if billable_acts and (billable_acts[0].date - last_billed_act.date).days >= 365:
840
                        return True
841
                    return False
842
            except:
843
                pass
844
        return False
845

    
846
    def automated_switch_state(self, modifier):
847
        def state_switcher(diag, act):
848
            if diag and (self.last_state.status.type == "ACCUEIL" or
849
                    self.last_state.status.type == "TRAITEMENT"):
850
                status = Status.objects.get(type="DIAGNOSTIC",
851
                    services__name='CMPP')
852
                try:
853
                    self.set_state(status, modifier, date_selected=act.date)
854
                except:
855
                    pass
856
            elif not diag and (self.last_state.status.type == "ACCUEIL" or
857
                    self.last_state.status.type == "DIAGNOSTIC"):
858
                status = Status.objects.get(type="TRAITEMENT",
859
                    services__name='CMPP')
860
                try:
861
                    self.set_state(status, modifier, date_selected=act.date)
862
                except:
863
                    pass
864
        # Only for CMPP and open files
865
        if not self.service.name == 'CMPP' or \
866
                self.last_state.status.type == "CLOS":
867
            return
868
        # Nothing to do if no act after the last state date
869
        last_acts = self.act_set.filter(date__gt=self.last_state.date_selected)
870
        if not last_acts:
871
            return
872
        # If the last act is billed, look at the healthcare type
873
        last_act = last_acts.latest('date')
874
        if last_act.is_billed:
875
            if not last_act.healthcare:
876
                # Billed but no healthcare, coming from imported billed acts
877
                return
878
            diag = False
879
            if hasattr(last_act.healthcare, 'cmpphealthcarediagnostic'):
880
                diag = True
881
            return state_switcher(diag, last_act)
882
        # Last act not billed, let's look if it is billable
883
        from calebasse.facturation import list_acts
884
        (acts_not_locked, days_not_locked, acts_not_valide,
885
        acts_not_billable, acts_pause, acts_per_hc, acts_losts) = \
886
            list_acts.list_acts_for_billing_CMPP_per_patient(self,
887
                datetime.today(), self.service)
888
        last_hc = None
889
        last_act_hc = None
890
        for hc, acts in acts_per_hc.iteritems():
891
            if len(acts) and (not last_act_hc or
892
                    acts[-1].date > last_act_hc.date):
893
                last_hc = hc
894
                last_act_hc = acts[-1]
895
        # There is a billable act after the last state so either it is diag
896
        # or it is treament
897
        if last_act_hc and last_hc and \
898
                last_act_hc.date > self.last_state.date_selected.date():
899
            if hasattr(last_hc, 'cmpphealthcarediagnostic'):
900
                state_switcher(True, last_act_hc)
901
            else:
902
                state_switcher(False, last_act_hc)
903

    
904
    def get_healthcare_status(self):
905
        today = date.today()
906
        current_hc_trait = None
907
        try:
908
            current_hc_trait = CmppHealthCareTreatment.objects.filter(
909
                patient=self, start_date__lte=today, end_date__gte=today
910
            ).latest('start_date')
911
        except:
912
            pass
913
        if not current_hc_trait:
914
            current_hc_diag = None
915
            try:
916
                current_hc_diag = CmppHealthCareDiagnostic.objects.filter(patient=self, start_date__lte=today).latest('start_date')
917
            except:
918
                pass
919
            if current_hc_diag and current_hc_diag.get_act_number() > len(current_hc_diag.act_set.all()):
920

    
921
                #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
922
                # Non parce que je peux ajouter une pc de traitement alors que je veux encore facturer sur la diag precedente.
923
                # Donc si j'ai un acte facturer en traitement alors la diag ne fonctionne plus.
924
                # 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.
925
                lasts_billed = Act.objects.filter(patient=self, is_billed = True, healthcare__isnull=False).order_by('-date')
926
                last_hc_date = None
927
                if lasts_billed:
928
                    last_hc_date = lasts_billed[0].healthcare.start_date
929
                if not last_hc_date or last_hc_date <= current_hc_diag.start_date:
930
                    # Prise en charge disponible
931
                    return (0, len(current_hc_diag.act_set.all()), current_hc_diag.get_act_number())
932
            last_hc_trait = None
933
            try:
934
                last_hc_trait = CmppHealthCareTreatment.objects.filter(patient=self).latest('start_date')
935
            except:
936
                pass
937
            if not last_hc_trait:
938
                if not current_hc_diag:
939
                    # Aucune PC
940
                    return (1, None)
941
                else:
942
                    # PC diag full, demander PC trait
943
                    return (2, current_hc_diag.get_act_number())
944
            if last_hc_trait.end_date < today:
945
                # Expirée
946
                #Test if rediagable
947
                return (3, last_hc_trait.end_date)
948
            if last_hc_trait.start_date > today:
949
                # N'a pas encore pris effet
950
                return (4, last_hc_trait.start_date)
951
            return (-1,)
952
        if current_hc_trait.get_act_number() > len(current_hc_trait.act_set.all()):
953
            # Pris en charge disponible
954
            return (5, len(current_hc_trait.act_set.all()), current_hc_trait.get_act_number())
955
        # Prise en charge au quota
956
        if not current_hc_trait.is_extended():
957
            # Peut être prolongée
958
            return (6, current_hc_trait.get_act_number())
959
        # Prise en charge saturée
960
        return (7, current_hc_trait.get_act_number(), current_hc_trait.end_date)
961
    # END Specific to cmpp healthcare
962

    
963

    
964
    @property
965
    def entry_date(self):
966
        d = self.filestate_set.filter(
967
                Q(status__type='DIAGNOSTIC') |
968
                Q(status__type='TRAITEMENT') |
969
                Q(status__type='SUIVI')). \
970
                        aggregate(Min('date_selected'))['date_selected__min']
971
        return d and d.date()
972

    
973

    
974
    @property
975
    def exit_date(self):
976
        if self.last_state.status.type != 'CLOS':
977
            return None
978
        d = self.filestate_set.filter(status__type='CLOS'). \
979
                    aggregate(Max('date_selected'))['date_selected__max']
980
        return d and d.date()
981

    
982
    @property
983
    def care_duration(self):
984
        # Duration between the first act present and the closing date.
985
        # If no closing date, end_date is the date of tha last act
986
        first_act_date = None
987
        try:
988
            first_act_date = self.act_set.filter(valide=True).order_by('date')[0].date
989
        except:
990
            return 0
991
        exit_date = self.exit_date
992
        if not exit_date:
993
            exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
994
        return (exit_date - first_act_date).days
995

    
996
    @property
997
    def care_duration_since_last_contact_or_first_act(self):
998
        # Duration between the first act present and the closing date.
999
        # If no closing date, end_date is the date of the last act
1000
        contacts = FileState.objects.filter(patient=self, status__type='ACCUEIL').order_by('date_selected')
1001
        last_contact = None
1002
        first_act_after_last_contact = None
1003
        if len(contacts) == 1:
1004
            last_contact = contacts[0]
1005
        elif len(contacts) > 1:
1006
            last_contact = contacts[len(contacts)-1]
1007
        if last_contact:
1008
            # inscription act
1009
            first_acts_after_last_contact = Act.objects.filter(patient=self, date__gte=last_contact.date_selected, valide=True).order_by('date')
1010
            if first_acts_after_last_contact:
1011
                first_act_after_last_contact = first_acts_after_last_contact[0]
1012
        if not contacts:
1013
            return self.care_duration
1014
        if not first_act_after_last_contact:
1015
            return 0
1016
        exit_date = self.exit_date
1017
        if not exit_date or exit_date < first_act_after_last_contact.date:
1018
            exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
1019
        return (exit_date - first_act_after_last_contact.date).days
1020

    
1021
    def care_duration_before_close_state(self, end_date=None):
1022
        if not end_date:
1023
            end_date = datetime.now()
1024
        last_close = None
1025
        try:
1026
            last_close = FileState.objects.filter(status__type='CLOS',
1027
                    patient=self, date_selected__lt=end_date).order_by('-date_selected')[0]
1028
        except:
1029
            pass
1030
        first_act = None
1031
        if last_close:
1032
            try:
1033
                first_act = Act.objects.filter(patient=self, valide=True, date__lte=end_date, date__gt=last_close.date_selected).order_by('date')[0]
1034
            except:
1035
                return 0
1036
        else:
1037
            try:
1038
                first_act = Act.objects.filter(patient=self, valide=True, date__lte=end_date).order_by('date')[0]
1039
            except:
1040
                return 0
1041
        return (end_date.date() - first_act.date).days + 1
1042

    
1043
def create_patient(first_name, last_name, service, creator,
1044
        date_selected=None):
1045
    logger.debug('create_patient: creation for patient %s %s in service %s '
1046
        'by %s' % (first_name, last_name, service, creator))
1047
    if not (first_name and last_name and service and creator):
1048
        raise Exception('Missing parameter to create a patient record.')
1049
    status = Status.objects.filter(type="ACCUEIL").filter(services=service)
1050
    if not status:
1051
        raise Exception('%s has no ACCEUIL status' % service.name)
1052
    patient = PatientRecord.objects.create(first_name=first_name,
1053
            last_name=last_name, service=service,
1054
            creator=creator)
1055
    fs = FileState(status=status[0], author=creator, previous_state=None)
1056
    if not date_selected:
1057
        date_selected = patient.created
1058
    fs.patient = patient
1059
    fs.date_selected = date_selected
1060
    fs.save()
1061
    patient.last_state = fs
1062
    patient.save()
1063
    patient.policyholder = patient.patientcontact
1064
    patient.save()
1065
    return patient
1066

    
1067
PatientRecord.DEFICIENCY_FIELDS = [field for field in PatientRecord._meta.get_all_field_names() if field.startswith('deficiency_')]
1068
PatientRecord.MISES_FIELDS = [field for field in PatientRecord._meta.get_all_field_names() if field.startswith('mises_')]
(5-5/12)