Projet

Général

Profil

Télécharger (43,3 ko) Statistiques
| Branche: | Tag: | Révision:

calebasse / calebasse / dossiers / models.py @ b057fff0

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

    
3
import logging
4
import os
5

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

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

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

    
23
from ..middleware.request import get_request
24

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

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

    
34

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

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

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

    
50

    
51
class HealthCare(models.Model):
52

    
53
    class Meta:
54
        app_label = 'dossiers'
55

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

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

    
74

    
75
class CmppHealthCareDiagnostic(HealthCare):
76

    
77
    class Meta:
78
        app_label = 'dossiers'
79

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

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

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

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

    
100

    
101
class CmppHealthCareTreatment(HealthCare):
102

    
103
    class Meta:
104
        app_label = 'dossiers'
105

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

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

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

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

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

    
140
    def del_prolongation(self):
141
        pass
142

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

    
154

    
155
class SessadHealthCareNotification(HealthCare):
156

    
157
    class Meta:
158
        app_label = 'dossiers'
159

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

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

    
173
class ProtectionStatus(NamedAbstractModel):
174

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

    
180
class ProtectionState(models.Model):
181

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

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

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

    
199
class Status(NamedAbstractModel):
200

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

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

    
209

    
210
class FileState(models.Model):
211

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

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

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

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

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

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

    
257
class PatientAddress(models.Model):
258

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

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

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

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

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

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

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

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

    
371
    def age(self):
372
        if not self.birthdate:
373
            return 'inconnu'
374
        now = datetime.today().date()
375
        age = relativedelta(now, self.birthdate)
376
        if age.years < 2:
377
            # for children < 2 years, return the number of months
378
            months = age.years * 12 + age.months
379
            if months:
380
                return '%s mois' % months
381
            return '%s jours' % age.days
382
        return '%s ans' % age.years
383

    
384

    
385
class PatientRecordManager(models.Manager):
386
    def for_service(self, service):
387
        return self.filter(service=service)
388

    
389
class PatientRecord(ServiceLinkedAbstractModel, PatientContact):
390
    objects = PatientRecordManager()
391

    
392
    class Meta:
393
        verbose_name = u'Dossier'
394
        verbose_name_plural = u'Dossiers'
395

    
396
    created = models.DateTimeField(u'création', auto_now_add=True)
397
    creator = \
398
        models.ForeignKey(User,
399
        verbose_name=u'Créateur dossier patient',
400
        editable=True)
401
    policyholder = models.ForeignKey('PatientContact',
402
            null=True, blank=True,
403
            verbose_name="Assuré", related_name="+",
404
            on_delete=models.SET_NULL)
405
    contacts = models.ManyToManyField('PatientContact',
406
            related_name='contact_of')
407
    nationality = models.CharField(verbose_name=u"Nationalité",
408
            max_length=70, null=True, blank=True)
409
    paper_id = models.CharField(max_length=6,
410
            verbose_name=u"N° dossier papier",
411
            null=True, blank=True)
412
    last_state = models.ForeignKey(FileState, related_name='+',
413
            null=True, on_delete=models.SET_NULL)
414
    comment = models.TextField(verbose_name=u"Commentaire",
415
            null=True, blank=True, default=None)
416
    pause = models.BooleanField(verbose_name=u"Pause facturation",
417
            default=False)
418
    pause_comment = models.TextField(verbose_name=u"Commentaire sur la pause facturation",
419
            null=True, blank=True, default=None)
420
    confidential = models.BooleanField(verbose_name=u"Confidentiel",
421
            default=False)
422
    socialisation_durations = models.ManyToManyField('ressources.SocialisationDuration',
423
            related_name='socialisation_duration_of')
424
    mdph_requests = models.ManyToManyField('ressources.MDPHRequest',
425
            related_name='mdph_requests_of')
426
    mdph_responses = models.ManyToManyField('ressources.MDPHResponse',
427
            related_name='mdph_responses_of')
428

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

    
502
    # Inscription motive
503
    analysemotive = models.ForeignKey('ressources.AnalyseMotive',
504
            verbose_name=u"Motif (analysé)",
505
            null=True, blank=True, default=None)
506
    familymotive = models.ForeignKey('ressources.FamilyMotive',
507
            verbose_name=u"Motif (famille)",
508
            null=True, blank=True, default=None)
509
    provenance = models.ForeignKey('ressources.Provenance',
510
            verbose_name=u"Conseilleur",
511
            null=True, blank=True, default=None)
512
    advicegiver = models.ForeignKey('ressources.AdviceGiver',
513
            verbose_name=u"Demandeur",
514
            null=True, blank=True, default=None)
515
    provenanceplace = models.ForeignKey('ressources.ProvenancePlace',
516
            verbose_name=u"Lieu de provenance",
517
            null=True, blank=True, default=None)
518

    
519
    # Out motive
520
    outmotive = models.ForeignKey('ressources.OutMotive',
521
            verbose_name=u"Motif de sortie",
522
            null=True, blank=True, default=None)
523
    outto = models.ForeignKey('ressources.OutTo',
524
            verbose_name=u"Orientation",
525
            null=True, blank=True, default=None)
526

    
527
    # Family
528
    sibship_place = models.IntegerField(verbose_name=u"Place dans la fratrie",
529
            null=True, blank=True, default=None)
530
    nb_children_family = models.IntegerField(verbose_name=u"Nombre d'enfants dans la fratrie",
531
            null=True, blank=True, default=None)
532
    parental_authority = models.ForeignKey('ressources.ParentalAuthorityType',
533
            verbose_name=u"Autorité parentale",
534
            null=True, blank=True, default=None)
535
    family_situation = models.ForeignKey('ressources.FamilySituationType',
536
            verbose_name=u"Situation familiale",
537
            null=True, blank=True, default=None)
538
    child_custody = models.ForeignKey('ressources.ParentalCustodyType',
539
            verbose_name=u"Garde parentale",
540
            null=True, blank=True, default=None)
541
    job_mother = models.ForeignKey('ressources.Job',
542
            related_name="job_mother",
543
            verbose_name=u"Profession de la mère",
544
            null=True, blank=True, default=None)
545
    job_father = models.ForeignKey('ressources.Job',
546
            related_name="job_father",
547
            verbose_name=u"Profession du père",
548
            null=True, blank=True, default=None)
549
    rm_mother = models.ForeignKey('ressources.MaritalStatusType',
550
            related_name="rm_mother",
551
            verbose_name=u"Régime matrimonial de la mère",
552
            null=True, blank=True, default=None)
553
    rm_father = models.ForeignKey('ressources.MaritalStatusType',
554
            related_name="rm_father",
555
            verbose_name=u"Régime matrimonial du père",
556
            null=True, blank=True, default=None)
557
    family_comment = models.TextField(verbose_name=u"Commentaire",
558
            null=True, blank=True, default=None)
559

    
560
    # Transport
561
    transporttype = models.ForeignKey('ressources.TransportType',
562
            verbose_name=u"Type de transport",
563
            null=True, blank=True, default=None)
564
    transportcompany = models.ForeignKey('ressources.TransportCompany',
565
            verbose_name=u"Compagnie de transport",
566
            null=True, blank=True, default=None)
567
    simple_appointment_transport = models.BooleanField(u'Afficher par défaut le transport sur les rendez-vous simples',
568
                                                       default=False)
569
    periodic_appointment_transport = models.BooleanField(u'Afficher par défaut le transport sur les rendez-vous réguliers',
570
                                                         default=False)
571

    
572
    # FollowUp
573
    coordinators = models.ManyToManyField('personnes.Worker',
574
            verbose_name=u"Coordinateurs",
575
            null=True, blank=True, default=None)
576
    externaldoctor = models.ForeignKey('personnes.ExternalTherapist',
577
            verbose_name=u"Médecin extérieur",
578
            null=True, blank=True, default=None)
579
    externalintervener = models.ForeignKey('personnes.ExternalWorker',
580
            verbose_name=u"Intervenant extérieur",
581
            null=True, blank=True, default=None)
582

    
583
    old_id = models.CharField(max_length=256,
584
            verbose_name=u'Ancien ID', blank=True, null=True)
585
    old_old_id = models.CharField(max_length=256,
586
            verbose_name=u'Ancien ancien ID', blank=True, null=True)
587

    
588
    def save(self, *args, **kwargs):
589
        if not getattr(self, 'service', None):
590
            raise Exception('The field service is mandatory.')
591
        super(PatientRecord, self).save(*args, **kwargs)
592

    
593
    def get_state(self):
594
        return self.last_state
595

    
596
    def get_initial_state(self):
597
        return self.filestate_set.order_by('date_selected')[0]
598

    
599
    def get_current_state(self):
600
        today = date.today()
601
        return self.get_state_at_day(today)
602

    
603
    def get_state_at_day(self, date):
604
        state = self.get_state()
605
        while(state):
606
            if datetime(state.date_selected.year,
607
                    state.date_selected.month, state.date_selected.day) <= \
608
                    datetime(date.year, date.month, date.day):
609
                return state
610
            state = state.previous_state
611
        return None
612

    
613
    def was_in_state_at_day(self, date, status_type):
614
        state_at_day = self.get_state_at_day(date)
615
        if state_at_day and state_at_day.status.type == status_type:
616
            return True
617
        return False
618

    
619
    def get_states_history(self):
620
        return self.filestate_set.order_by('date_selected')
621

    
622
    def get_states_history_with_duration(self):
623
        '''
624
        Return the state history with for each state its duration.
625
        If the last state is in the past, the duration is counted until today.
626
        If the last state is in the future, the duration is not set.
627
        '''
628
        history = self.get_states_history()
629
        history_with_duration = list()
630
        today = datetime.today()
631
        i = 0
632
        for state in history:
633
            history_with_duration.append([state, None])
634
            if i != 0:
635
                history_with_duration[i-1][1] = state.date_selected - history_with_duration[i-1][0].date_selected
636
            if i == len(history)-1 and state.date_selected <= today:
637
                history_with_duration[i][1] = today - history_with_duration[i][0].date_selected
638
            i += 1
639
        return history_with_duration
640

    
641
    def can_be_deleted(self):
642
        for act in self.act_set.all():
643
            if act.is_state('VALIDE'):
644
                return False
645
        return True
646

    
647
    def delete(self, *args, **kwargs):
648
        if self.can_be_deleted():
649
            obj_id = self.id
650
            super(PatientRecord, self).delete(*args, **kwargs)
651

    
652
    def get_ondisk_directory(self, service):
653
        if not settings.PATIENT_FILES_BASE_DIRECTORY:
654
            return None
655

    
656
        dirnames = []
657
        dirname = self.last_name.upper()
658
        dirnames.append(dirname)
659
        if self.first_name:
660
            dirname = '%s %s' % (dirname, self.first_name)
661
            dirnames.append(dirname)
662
        if self.paper_id:
663
            dirname = '%s %s' % (dirname, self.paper_id)
664
            dirnames.append(dirname)
665

    
666
        for i, dirname in enumerate(dirnames):
667
            fullpath = os.path.join(settings.PATIENT_FILES_BASE_DIRECTORY, service, dirname)
668
            try:
669
                next_fullpath = os.path.join(settings.PATIENT_FILES_BASE_DIRECTORY, service, dirnames[i+1])
670
            except IndexError:
671
                pass
672
            else:
673
                if os.path.exists(fullpath) and not os.path.exists(next_fullpath):
674
                    os.rename(fullpath, next_fullpath)
675
                continue
676
            if not os.path.exists(fullpath):
677
                os.makedirs(fullpath)
678
            for subdir in settings.PATIENT_SUBDIRECTORIES:
679
                subdir_fullpath = os.path.join(fullpath, subdir)
680
                if not os.path.exists(subdir_fullpath):
681
                    os.makedirs(subdir_fullpath)
682
        return fullpath
683

    
684
    def get_client_side_directory(self, service):
685
        directory = self.get_ondisk_directory(service)
686
        if not directory:
687
            return None
688
        if not settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY:
689
            return None
690
        return os.path.join(settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY,
691
                            directory[len(settings.PATIENT_FILES_BASE_DIRECTORY)+1:])
692

    
693
    def set_state(self, status, author, date_selected=None, comment=None):
694
        if not author:
695
            raise Exception('Missing author to set state')
696
        if not date_selected:
697
            date_selected = datetime.now()
698
        current_state = self.get_state()
699
        if not current_state:
700
            raise Exception('Invalid patient record. '
701
                'Missing current state.')
702
        if isinstance(date_selected, date):
703
            date_selected = datetime(year=date_selected.year,
704
                month=date_selected.month, day=date_selected.day)
705
        if date_selected < current_state.date_selected:
706
            raise Exception('You cannot set a state starting the %s that '
707
                'is before the previous state starting at day %s.' % \
708
                (str(date_selected), str(current_state.date_selected)))
709
        filestate = FileState.objects.create(patient=self, status=status,
710
            date_selected=date_selected, author=author, comment=comment,
711
            previous_state=current_state)
712
        self.last_state = filestate
713
        self.save()
714

    
715
    def change_day_selected_of_state(self, state, new_date):
716
        if state.previous_state:
717
            if new_date < state.previous_state.date_selected:
718
                raise Exception('You cannot set a state starting the %s '
719
                    'before the previous state starting at day %s.' % \
720
                    (str(new_date), str(state.previous_state.date_selected)))
721
        next_state = state.get_next_state()
722
        if next_state:
723
            if new_date > next_state.date_selected:
724
                raise Exception('You cannot set a state starting the %s '
725
                    'after the following state starting at day %s.' % \
726
                    (str(new_date), str(next_state.date_selected)))
727
        state.date_selected = new_date
728
        state.save()
729

    
730
    def remove_state(self, state):
731
        if state.patient.id != self.id:
732
            raise Exception('The state given is not about this patient '
733
                'record but about %s' % state.patient)
734
        next_state = state.get_next_state()
735
        if not next_state:
736
            self.remove_last_state()
737
        else:
738
            next_state.previous_state = state.previous_state
739
            next_state.save()
740
            state.delete()
741

    
742
    def remove_last_state(self):
743
        try:
744
            self.get_state().delete()
745
        except:
746
            pass
747

    
748
    def get_protection_state_at_date(self, date):
749
        try:
750
            return self.protectionstate_set.exclude(end_date__lt=date). \
751
                exclude(start_date__gt=date).latest('start_date')
752
        except:
753
            return None
754

    
755
    def get_next_rdv(self):
756
        from views_utils import get_next_rdv
757
        return get_next_rdv(self)
758

    
759
    # START Specific to sessad healthcare
760
    def get_last_notification(self):
761
        return SessadHealthCareNotification.objects.filter(patient=self, ).\
762
            latest('end_date')
763

    
764
    def days_before_notification_expiration(self):
765
        today = datetime.today()
766
        notification = self.get_last_notification(self)
767
        if not notification:
768
            return 0
769
        if notification.end_date < today:
770
            return 0
771
        else:
772
            return notification.end_date - today
773
    # END Specific to sessad healthcare
774

    
775
    # START Specific to cmpp healthcare
776
    def create_diag_healthcare(self, modifier):
777
        """
778
            Gestion de l'inscription automatique.
779

    
780
            Si un premier acte est validé alors une prise en charge
781
            diagnostique est ajoutée. Cela fera basculer le dossier dans l'état
782
            en diagnostic.
783

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

    
790
        """
791
        acts = Act.objects.filter(validation_locked=False,
792
            patient__service=self.service)
793
        days_not_locked = sorted(set(acts.values_list('date', flat=True)))
794
        acts = self.act_set.filter(validation_locked=True,
795
            valide=True, is_lost=False, is_billed=False)
796
        acts = acts.exclude(date__in=days_not_locked)
797
        acts = acts.order_by('date')
798
        pause_query = Q(pause=True)
799
        billable_query = Q(act_type__billable=True, switch_billable=False) | \
800
                Q(act_type__billable=False, switch_billable=True)
801
        billable_acts = acts.filter(~pause_query & billable_query)
802

    
803
        if not CmppHealthCareDiagnostic.objects.filter(patient=self).exists() \
804
                and billable_acts:
805
            # Pas de prise en charge, on recherche l'acte facturable le plus
806
            # ancien, on crée une pc diag à la même date.
807
            CmppHealthCareDiagnostic(patient=self, author=modifier,
808
                start_date=billable_acts[0].date).save()
809
        else:
810
            # On recherche l'acte facturable non facturé le plus ancien après
811
            # le dernier acte facturé et on regarde s'il a plus d'un an
812
            try:
813
                last_billed_act = self.act_set.filter(is_billed=True).\
814
                    latest('date')
815
                if last_billed_act and billable_acts:
816
                    billable_acts = billable_acts.filter(date__gte=last_billed_act.date)
817
                    if billable_acts and (billable_acts[0].date - last_billed_act.date).days >= 365:
818
                        return True
819
                    return False
820
            except:
821
                pass
822
        return False
823

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

    
882
    def get_healthcare_status(self):
883
        today = date.today()
884
        current_hc_trait = None
885
        try:
886
            current_hc_trait = CmppHealthCareTreatment.objects.filter(
887
                patient=self, start_date__lte=today, end_date__gte=today
888
            ).latest('start_date')
889
        except:
890
            pass
891
        if not current_hc_trait:
892
            current_hc_diag = None
893
            try:
894
                current_hc_diag = CmppHealthCareDiagnostic.objects.filter(patient=self, start_date__lte=today).latest('start_date')
895
            except:
896
                pass
897
            if current_hc_diag and current_hc_diag.get_act_number() > len(current_hc_diag.act_set.all()):
898

    
899
                #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
900
                # Non parce que je peux ajouter une pc de traitement alors que je veux encore facturer sur la diag precedente.
901
                # Donc si j'ai un acte facturer en traitement alors la diag ne fonctionne plus.
902
                # 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.
903
                lasts_billed = Act.objects.filter(patient=self, is_billed = True, healthcare__isnull=False).order_by('-date')
904
                last_hc_date = None
905
                if lasts_billed:
906
                    last_hc_date = lasts_billed[0].healthcare.start_date
907
                if not last_hc_date or last_hc_date <= current_hc_diag.start_date:
908
                    # Prise en charge disponible
909
                    return (0, len(current_hc_diag.act_set.all()), current_hc_diag.get_act_number())
910
            last_hc_trait = None
911
            try:
912
                last_hc_trait = CmppHealthCareTreatment.objects.filter(patient=self).latest('start_date')
913
            except:
914
                pass
915
            if not last_hc_trait:
916
                if not current_hc_diag:
917
                    # Aucune PC
918
                    return (1, None)
919
                else:
920
                    # PC diag full, demander PC trait
921
                    return (2, current_hc_diag.get_act_number())
922
            if last_hc_trait.end_date < today:
923
                # Expirée
924
                #Test if rediagable
925
                return (3, last_hc_trait.end_date)
926
            if last_hc_trait.start_date > today:
927
                # N'a pas encore pris effet
928
                return (4, last_hc_trait.start_date)
929
            return (-1,)
930
        if current_hc_trait.get_act_number() > len(current_hc_trait.act_set.all()):
931
            # Pris en charge disponible
932
            return (5, len(current_hc_trait.act_set.all()), current_hc_trait.get_act_number())
933
        # Prise en charge au quota
934
        if not current_hc_trait.is_extended():
935
            # Peut être prolongée
936
            return (6, current_hc_trait.get_act_number())
937
        # Prise en charge saturée
938
        return (7, current_hc_trait.get_act_number(), current_hc_trait.end_date)
939
    # END Specific to cmpp healthcare
940

    
941

    
942
    @property
943
    def entry_date(self):
944
        d = self.filestate_set.filter(
945
                Q(status__type='DIAGNOSTIC') |
946
                Q(status__type='TRAITEMENT') |
947
                Q(status__type='SUIVI')). \
948
                        aggregate(Min('date_selected'))['date_selected__min']
949
        return d and d.date()
950

    
951

    
952
    @property
953
    def exit_date(self):
954
        if self.last_state.status.type != 'CLOS':
955
            return None
956
        d = self.filestate_set.filter(status__type='CLOS'). \
957
                    aggregate(Max('date_selected'))['date_selected__max']
958
        return d and d.date()
959

    
960
    @property
961
    def care_duration(self):
962
        # Duration between the first act present and the closing date.
963
        # If no closing date, end_date is the date of tha last act
964
        first_act_date = None
965
        try:
966
            first_act_date = self.act_set.filter(valide=True).order_by('date')[0].date
967
        except:
968
            return 0
969
        exit_date = self.exit_date
970
        if not exit_date:
971
            exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
972
        return (exit_date - first_act_date).days
973

    
974
    @property
975
    def care_duration_since_last_contact_or_first_act(self):
976
        # Duration between the first act present and the closing date.
977
        # If no closing date, end_date is the date of the last act
978
        contacts = FileState.objects.filter(patient=self, status__type='ACCUEIL').order_by('date_selected')
979
        last_contact = None
980
        first_act_after_last_contact = None
981
        if len(contacts) == 1:
982
            last_contact = contacts[0]
983
        elif len(contacts) > 1:
984
            last_contact = contacts[len(contacts)-1]
985
        if last_contact:
986
            # inscription act
987
            first_acts_after_last_contact = Act.objects.filter(patient=self, date__gte=last_contact.date_selected, valide=True).order_by('date')
988
            if first_acts_after_last_contact:
989
                first_act_after_last_contact = first_acts_after_last_contact[0]
990
        if not contacts:
991
            return self.care_duration
992
        if not first_act_after_last_contact:
993
            return 0
994
        exit_date = self.exit_date
995
        if not exit_date or exit_date < first_act_after_last_contact.date:
996
            exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
997
        return (exit_date - first_act_after_last_contact.date).days
998

    
999
    def care_duration_before_close_state(self, end_date=None):
1000
        if not end_date:
1001
            end_date = datetime.now()
1002
        last_close = None
1003
        try:
1004
            last_close = FileState.objects.filter(status__type='CLOS',
1005
                    patient=self, date_selected__lt=end_date).order_by('-date_selected')[0]
1006
        except:
1007
            pass
1008
        first_act = None
1009
        if last_close:
1010
            try:
1011
                first_act = Act.objects.filter(patient=self, valide=True, date__lte=end_date, date__gt=last_close.date_selected).order_by('date')[0]
1012
            except:
1013
                return 0
1014
        else:
1015
            try:
1016
                first_act = Act.objects.filter(patient=self, valide=True, date__lte=end_date).order_by('date')[0]
1017
            except:
1018
                return 0
1019
        return (end_date.date() - first_act.date).days + 1
1020

    
1021
def create_patient(first_name, last_name, service, creator,
1022
        date_selected=None):
1023
    logger.debug('create_patient: creation for patient %s %s in service %s '
1024
        'by %s' % (first_name, last_name, service, creator))
1025
    if not (first_name and last_name and service and creator):
1026
        raise Exception('Missing parameter to create a patient record.')
1027
    status = Status.objects.filter(type="ACCUEIL").filter(services=service)
1028
    if not status:
1029
        raise Exception('%s has no ACCEUIL status' % service.name)
1030
    patient = PatientRecord.objects.create(first_name=first_name,
1031
            last_name=last_name, service=service,
1032
            creator=creator)
1033
    fs = FileState(status=status[0], author=creator, previous_state=None)
1034
    if not date_selected:
1035
        date_selected = patient.created
1036
    fs.patient = patient
1037
    fs.date_selected = date_selected
1038
    fs.save()
1039
    patient.last_state = fs
1040
    patient.save()
1041
    patient.policyholder = patient.patientcontact
1042
    patient.save()
1043
    return patient
1044

    
1045
PatientRecord.DEFICIENCY_FIELDS = [field for field in PatientRecord._meta.get_all_field_names() if field.startswith('deficiency_')]
1046
PatientRecord.MISES_FIELDS = [field for field in PatientRecord._meta.get_all_field_names() if field.startswith('mises_')]
1047

    
(5-5/12)