Project

General

Profile

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

calebasse / calebasse / dossiers / models.py @ 3c5df84d

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
import reversion
17

    
18
from calebasse.choices import TYPE_OF_CONTRACT_CHOICES, DEFICIENCY_CHOICES
19
from calebasse.models import PhoneNumberField, ZipCodeField
20
from calebasse.personnes.models import People
21
from calebasse.ressources.models import (ServiceLinkedAbstractModel,
22
        NamedAbstractModel)
23
from calebasse.actes.models import Act
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
reversion.register(CmppHealthCareDiagnostic, follow=['healthcare_ptr'])
174
reversion.register(CmppHealthCareTreatment, follow=['healthcare_ptr'])
175
reversion.register(SessadHealthCareNotification, follow=['healthcare_ptr'])
176

    
177
class ProtectionStatus(NamedAbstractModel):
178

    
179
    class Meta:
180
        app_label = 'dossiers'
181
        verbose_name = u"Statut d'une mesure de protection"
182
        verbose_name_plural = u"Statuts d'une mesure de protection"
183

    
184
class ProtectionState(models.Model):
185

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

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

    
200
    def __unicode__(self):
201
        return self.status.name + ' ' + str(self.start_date)
202

    
203
class Status(NamedAbstractModel):
204

    
205
    class Meta:
206
        app_label = 'dossiers'
207
        verbose_name = u"Statut d'un état"
208
        verbose_name_plural = u"Statuts d'un état"
209

    
210
    type = models.CharField(max_length=80)
211
    services = models.ManyToManyField('ressources.Service')
212

    
213

    
214
class FileState(models.Model):
215

    
216
    class Meta:
217
        app_label = 'dossiers'
218
        verbose_name = u'Etat du dossier patient'
219
        verbose_name_plural = u'Etats du dossier patient'
220

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

    
235
    def get_next_state(self):
236
        try:
237
            return FileState.objects.get(previous_state=self)
238
        except:
239
            return None
240

    
241
    def save(self, **kwargs):
242
        self.date_selected = \
243
                datetime(self.date_selected.year,
244
                        self.date_selected.month, self.date_selected.day)
245
        super(FileState, self).save(**kwargs)
246

    
247
    def __unicode__(self):
248
        return self.status.name + ' ' + str(self.date_selected)
249

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

    
260
class PatientAddress(models.Model):
261

    
262
    display_name = models.CharField(max_length=276,
263
            verbose_name=u'Adresse complète', editable=False)
264
    phone = PhoneNumberField(verbose_name=u"Téléphone", blank=True, null=True)
265
    fax = PhoneNumberField(verbose_name=u"Fax", blank=True, null=True)
266
    place_of_life = models.BooleanField(verbose_name=u"Lieu de vie")
267
    number = models.CharField(max_length=12,
268
            verbose_name=u"Numéro", 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
282

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

    
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
                print str(e)
368
                return None
369
        return None
370

    
371

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

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

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

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

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

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

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

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

    
547
    # Transport
548
    transporttype = models.ForeignKey('ressources.TransportType',
549
            verbose_name=u"Type de transport",
550
            null=True, blank=True, default=None)
551
    transportcompany = models.ForeignKey('ressources.TransportCompany',
552
            verbose_name=u"Compagnie de transport",
553
            null=True, blank=True, default=None)
554

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

    
566
    old_id = models.CharField(max_length=256,
567
            verbose_name=u'Ancien ID', blank=True, null=True)
568
    old_old_id = models.CharField(max_length=256,
569
            verbose_name=u'Ancien ancien ID', blank=True, null=True)
570

    
571
    def save(self, *args, **kwargs):
572
        if not getattr(self, 'service', None):
573
            raise Exception('The field service is mandatory.')
574
        super(PatientRecord, self).save(*args, **kwargs)
575

    
576
    def get_state(self):
577
        return self.last_state
578

    
579
    def get_initial_state(self):
580
        return self.filestate_set.order_by('date_selected')[0]
581

    
582
    def get_current_state(self):
583
        today = date.today()
584
        return self.get_state_at_day(today)
585

    
586
    def get_state_at_day(self, date):
587
        state = self.get_state()
588
        while(state):
589
            if datetime(state.date_selected.year,
590
                    state.date_selected.month, state.date_selected.day) <= \
591
                    datetime(date.year, date.month, date.day):
592
                return state
593
            state = state.previous_state
594
        return self.get_state()
595

    
596
    def was_in_state_at_day(self, date, status_type):
597
        state_at_day = self.get_state_at_day(date)
598
        if state_at_day and state_at_day.status.type == status_type:
599
            return True
600
        return False
601

    
602
    def get_states_history(self):
603
        return self.filestate_set.order_by('date_selected')
604

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

    
624
    def can_be_deleted(self):
625
        for act in self.act_set.all():
626
            if act.is_state('VALIDE'):
627
                return False
628
        return True
629

    
630
    def delete(self, *args, **kwargs):
631
        if self.can_be_deleted():
632
            super(PatientRecord, self).delete(*args, **kwargs)
633

    
634
    def get_ondisk_directory(self, service):
635
        if not settings.PATIENT_FILES_BASE_DIRECTORY:
636
            return None
637

    
638
        dirnames = []
639
        dirname = self.last_name.upper()
640
        dirnames.append(dirname)
641
        if self.first_name:
642
            dirname = '%s %s' % (dirname, self.first_name)
643
            dirnames.append(dirname)
644
        if self.paper_id:
645
            dirname = '%s %s' % (dirname, self.paper_id)
646
            dirnames.append(dirname)
647

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

    
666
    def get_client_side_directory(self, service):
667
        directory = self.get_ondisk_directory(service)
668
        if not directory:
669
            return None
670
        if not settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY:
671
            return None
672
        return os.path.join(settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY,
673
                            directory[len(settings.PATIENT_FILES_BASE_DIRECTORY)+1:])
674

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

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

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

    
724
    def remove_last_state(self):
725
        try:
726
            self.get_state().delete()
727
        except:
728
            pass
729

    
730
    def get_protection_state_at_date(self, date):
731
        try:
732
            return self.protectionstate_set.exclude(end_date__lt=date). \
733
                exclude(start_date__gt=date).latest('start_date')
734
        except:
735
            return None
736

    
737
    # START Specific to sessad healthcare
738
    def get_last_notification(self):
739
        return SessadHealthCareNotification.objects.filter(patient=self, ).\
740
            latest('end_date')
741

    
742
    def days_before_notification_expiration(self):
743
        today = datetime.today()
744
        notification = self.get_last_notification(self)
745
        if not notification:
746
            return 0
747
        if notification.end_date < today:
748
            return 0
749
        else:
750
            return notification.end_date - today
751
    # END Specific to sessad healthcare
752

    
753
    # START Specific to cmpp healthcare
754
    def create_diag_healthcare(self, modifier):
755
        """
756
            Gestion de l'inscription automatique.
757

    
758
            Si un premier acte est validé alors une prise en charge
759
            diagnostique est ajoutée. Cela fera basculer le dossier dans l'état
760
            en diagnostic.
761

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

    
768
        """
769
        acts = Act.objects.filter(validation_locked=False,
770
            patient__service=self.service)
771
        days_not_locked = sorted(set(acts.values_list('date', flat=True)))
772
        acts = self.act_set.filter(validation_locked=True,
773
            valide=True, is_lost=False, is_billed=False)
774
        acts = acts.exclude(date__in=days_not_locked)
775
        acts = acts.order_by('date')
776
        pause_query = Q(pause=True)
777
        billable_query = Q(act_type__billable=True, switch_billable=False) | \
778
                Q(act_type__billable=False, switch_billable=True)
779
        billable_acts = acts.filter(~pause_query & billable_query)
780

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

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

    
860
    def get_healthcare_status(self):
861
        today = date.today()
862
        current_hc_trait = None
863
        try:
864
            current_hc_trait = CmppHealthCareTreatment.objects.filter(
865
                patient=self, start_date__lte=today, end_date__gte=today
866
            ).latest('start_date')
867
        except:
868
            pass
869
        if not current_hc_trait:
870
            current_hc_diag = None
871
            try:
872
                current_hc_diag = CmppHealthCareDiagnostic.objects.filter(patient=self, start_date__lte=today).latest('start_date')
873
            except:
874
                pass
875
            if current_hc_diag and current_hc_diag.get_act_number() > len(current_hc_diag.act_set.all()):
876

    
877
                #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
878
                # Non parce que je peux ajouter une pc de traitement alors que je veux encore facturer sur la diag precedente.
879
                # Donc si j'ai un acte facturer en traitement alors la diag ne fonctionne plus.
880
                # 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.
881
                lasts_billed = Act.objects.filter(patient=self, is_billed = True, healthcare__isnull=False).order_by('-date')
882
                last_hc_date = None
883
                if lasts_billed:
884
                    last_hc_date = lasts_billed[0].healthcare.start_date
885
                if not last_hc_date or last_hc_date <= current_hc_diag.start_date:
886
                    # Prise en charge disponible
887
                    return (0, len(current_hc_diag.act_set.all()), current_hc_diag.get_act_number())
888
            last_hc_trait = None
889
            try:
890
                last_hc_trait = CmppHealthCareTreatment.objects.filter(patient=self).latest('start_date')
891
            except:
892
                pass
893
            if not last_hc_trait:
894
                if not current_hc_diag:
895
                    # Aucune PC
896
                    return (1, None)
897
                else:
898
                    # PC diag full, demander PC trait
899
                    return (2, current_hc_diag.get_act_number())
900
            if last_hc_trait.end_date < today:
901
                # Expirée
902
                #Test if rediagable
903
                return (3, last_hc_trait.end_date)
904
            if last_hc_trait.start_date > today:
905
                # N'a pas encore pris effet
906
                return (4, last_hc_trait.start_date)
907
            return (-1,)
908
        if current_hc_trait.get_act_number() > len(current_hc_trait.act_set.all()):
909
            # Pris en charge disponible
910
            return (5, len(current_hc_trait.act_set.all()), current_hc_trait.get_act_number())
911
        # Prise en charge au quota
912
        if not current_hc_trait.is_extended():
913
            # Peut être prolongée
914
            return (6, current_hc_trait.get_act_number())
915
        # Prise en charge saturée
916
        return (7, current_hc_trait.get_act_number(), current_hc_trait.end_date)
917
    # END Specific to cmpp healthcare
918

    
919

    
920
    @property
921
    def entry_date(self):
922
        d = self.filestate_set.filter(
923
                Q(status__type='DIAGNOSTIC') |
924
                Q(status__type='TRAITEMENT') |
925
                Q(status__type='SUIVI')). \
926
                        aggregate(Min('date_selected'))['date_selected__min']
927
        return d and d.date()
928

    
929

    
930
    @property
931
    def exit_date(self):
932
        if self.last_state.status.type != 'CLOS':
933
            return None
934
        d = self.filestate_set.filter(status__type='CLOS'). \
935
                    aggregate(Max('date_selected'))['date_selected__max']
936
        return d and d.date()
937

    
938
    @property
939
    def care_duration(self):
940
        # Duration between the first act present and the closing date.
941
        # If no closing date, end_date is the date of tha last act
942
        first_act_date = None
943
        try:
944
            first_act_date = self.act_set.filter(valide=True).order_by('date')[0].date
945
        except:
946
            return 0
947
        exit_date = self.exit_date
948
        if not exit_date:
949
            exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
950
        return (exit_date - first_act_date).days
951

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

    
977
reversion.register(PatientRecord, follow=['people_ptr'])
978

    
979

    
980
def create_patient(first_name, last_name, service, creator,
981
        date_selected=None):
982
    logger.debug('create_patient: creation for patient %s %s in service %s '
983
        'by %s' % (first_name, last_name, service, creator))
984
    if not (first_name and last_name and service and creator):
985
        raise Exception('Missing parameter to create a patient record.')
986
    status = Status.objects.filter(type="ACCUEIL").filter(services=service)
987
    if not status:
988
        raise Exception('%s has no ACCEUIL status' % service.name)
989
    patient = PatientRecord.objects.create(first_name=first_name,
990
            last_name=last_name, service=service,
991
            creator=creator)
992
    fs = FileState(status=status[0], author=creator, previous_state=None)
993
    if not date_selected:
994
        date_selected = patient.created
995
    fs.patient = patient
996
    fs.date_selected = date_selected
997
    fs.save()
998
    patient.last_state = fs
999
    patient.save()
1000
    patient.policyholder = patient.patientcontact
1001
    patient.save()
1002
    return patient
(5-5/11)