Project

General

Profile

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

calebasse / calebasse / dossiers / models.py @ 2a03cc83

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
    number = models.CharField(max_length=12,
266
            verbose_name=u"Numéro", blank=True, null=True)
267
    recipient = models.CharField(max_length=100,
268
            verbose_name=u"Destinataire", blank=True, null=True)
269
    street = models.CharField(max_length=100,
270
            verbose_name=u"Rue", blank=True, null=True)
271
    address_complement = models.CharField(max_length=100,
272
            blank=True, null=True,
273
            verbose_name=u"Complément d'adresse")
274
    zip_code = ZipCodeField(verbose_name=u"Code postal", blank=True, null=True)
275
    city = models.CharField(max_length=60,
276
            verbose_name=u"Ville", blank=True, null=True)
277
    comment = models.TextField(verbose_name=u"Commentaire",
278
            null=True, blank=True)
279

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

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

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

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

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

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

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

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

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

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

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

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

    
401
        return ' et '.join(components)
402

    
403

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
962

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

    
972

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

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

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

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

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

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