Projet

Général

Profil

Télécharger (42,9 ko) Statistiques
| Branche: | Tag: | Révision:

calebasse / calebasse / dossiers / models.py @ a01d85be

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

    
3
import logging
4
import os
5

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

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

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

    
23
from ..middleware.request import get_request
24

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

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

    
34

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

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

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

    
50

    
51
class HealthCare(models.Model):
52

    
53
    class Meta:
54
        app_label = 'dossiers'
55

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

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

    
74

    
75
class CmppHealthCareDiagnostic(HealthCare):
76

    
77
    class Meta:
78
        app_label = 'dossiers'
79

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

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

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

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

    
100

    
101
class CmppHealthCareTreatment(HealthCare):
102

    
103
    class Meta:
104
        app_label = 'dossiers'
105

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

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

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

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

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

    
140
    def del_prolongation(self):
141
        pass
142

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

    
154

    
155
class SessadHealthCareNotification(HealthCare):
156

    
157
    class Meta:
158
        app_label = 'dossiers'
159

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

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

    
173
class ProtectionStatus(NamedAbstractModel):
174

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

    
180
class ProtectionState(models.Model):
181

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

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

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

    
199
class Status(NamedAbstractModel):
200

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

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

    
209

    
210
class FileState(models.Model):
211

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

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

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

    
237
    def save(self, **kwargs):
238
        self.date_selected = \
239
                datetime(self.date_selected.year,
240
                        self.date_selected.month, self.date_selected.day)
241
        super(FileState, self).save(**kwargs)
242
        get_request().record('filestate-save', '{obj_id} saved by {user} from {ip}', obj_id=self.id)
243

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

    
247
    def delete(self, *args, **kwargs):
248
        next_state = self.get_next_state()
249
        if next_state and self.previous_state:
250
            next_state.previous_state = self.previous_state
251
            next_state.save()
252
        if self.patient.last_state == self:
253
            self.patient.last_state = self.previous_state
254
            self.patient.save()
255
        obj_id = self.id
256
        super(FileState, self).delete(*args, **kwargs)
257
        get_request().record('filestate-delete', '{obj_id} deleted by {user} from {ip}',
258
                             obj_id=obj_id)
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
        get_request().record('patientaddress-save', '{obj_id} saved by {user} from {ip}', obj_id=self.id)
301

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

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

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

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

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

    
375

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

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

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

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

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

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

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

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

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

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

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

    
575
    def save(self, *args, **kwargs):
576
        if not getattr(self, 'service', None):
577
            raise Exception('The field service is mandatory.')
578
        super(PatientRecord, self).save(*args, **kwargs)
579
        get_request().record('patientrecord-save', '{obj_id} saved by {user} from {ip}', obj_id=self.id)
580

    
581
    def get_state(self):
582
        return self.last_state
583

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

    
587
    def get_current_state(self):
588
        today = date.today()
589
        return self.get_state_at_day(today)
590

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

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

    
607
    def get_states_history(self):
608
        return self.filestate_set.order_by('date_selected')
609

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

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

    
635
    def delete(self, *args, **kwargs):
636
        if self.can_be_deleted():
637
            obj_id = self.id
638
            super(PatientRecord, self).delete(*args, **kwargs)
639
            get_request().record('patientrecord-delete', '{obj_id} by {user} from {ip}', obj_id=obj_id)
640

    
641
    def get_ondisk_directory(self, service):
642
        if not settings.PATIENT_FILES_BASE_DIRECTORY:
643
            return None
644

    
645
        dirnames = []
646
        dirname = self.last_name.upper()
647
        dirnames.append(dirname)
648
        if self.first_name:
649
            dirname = '%s %s' % (dirname, self.first_name)
650
            dirnames.append(dirname)
651
        if self.paper_id:
652
            dirname = '%s %s' % (dirname, self.paper_id)
653
            dirnames.append(dirname)
654

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

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

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

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

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

    
731
    def remove_last_state(self):
732
        try:
733
            self.get_state().delete()
734
        except:
735
            pass
736

    
737
    def get_protection_state_at_date(self, date):
738
        try:
739
            return self.protectionstate_set.exclude(end_date__lt=date). \
740
                exclude(start_date__gt=date).latest('start_date')
741
        except:
742
            return None
743

    
744
    # START Specific to sessad healthcare
745
    def get_last_notification(self):
746
        return SessadHealthCareNotification.objects.filter(patient=self, ).\
747
            latest('end_date')
748

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

    
760
    # START Specific to cmpp healthcare
761
    def create_diag_healthcare(self, modifier):
762
        """
763
            Gestion de l'inscription automatique.
764

    
765
            Si un premier acte est validé alors une prise en charge
766
            diagnostique est ajoutée. Cela fera basculer le dossier dans l'état
767
            en diagnostic.
768

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

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

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

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

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

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

    
926

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

    
936

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

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

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

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

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