Project

General

Profile

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

calebasse / calebasse / dossiers / models.py @ 243e22a9

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

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

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

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

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

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

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

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

    
374

    
375
class PatientRecordManager(models.Manager):
376
    def for_service(self, service):
377
        return self.filter(service=service)
378

    
379
class PatientRecord(ServiceLinkedAbstractModel, PatientContact):
380
    objects = PatientRecordManager()
381

    
382
    class Meta:
383
        verbose_name = u'Dossier'
384
        verbose_name_plural = u'Dossiers'
385

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
633
    def delete(self, *args, **kwargs):
634
        if self.can_be_deleted():
635
            super(PatientRecord, self).delete(*args, **kwargs)
636

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
922

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

    
932

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

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

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

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

    
1002
reversion.register(PatientRecord, follow=['people_ptr'])
1003

    
1004

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