Projet

Général

Profil

Télécharger (44 ko) Statistiques
| Branche: | Tag: | Révision:

calebasse / calebasse / dossiers / models.py @ 1af9e727

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

    
3
import logging
4
import os
5

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

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

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

    
23
from ..middleware.request import get_request
24
from ..utils import get_service_setting
25

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

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

    
35

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

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

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

    
51

    
52
class HealthCare(models.Model):
53

    
54
    class Meta:
55
        app_label = 'dossiers'
56

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

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

    
75

    
76
class CmppHealthCareDiagnostic(HealthCare):
77

    
78
    class Meta:
79
        app_label = 'dossiers'
80

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

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

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

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

    
101

    
102
class CmppHealthCareTreatment(HealthCare):
103

    
104
    class Meta:
105
        app_label = 'dossiers'
106

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

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

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

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

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

    
141
    def del_prolongation(self):
142
        pass
143

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

    
155

    
156
class SessadHealthCareNotification(HealthCare):
157

    
158
    class Meta:
159
        app_label = 'dossiers'
160

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

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

    
174
class ProtectionStatus(NamedAbstractModel):
175

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

    
181
class ProtectionState(models.Model):
182

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

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

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

    
200
class Status(NamedAbstractModel):
201

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

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

    
210

    
211
class FileState(models.Model):
212

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

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

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

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

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

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

    
258
class PatientAddress(models.Model):
259

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

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

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

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

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

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

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

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

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

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

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

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

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

    
401
        return ' et '.join(components)
402

    
403

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

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

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

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

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

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

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

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

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

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

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

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

    
612
    def get_state(self):
613
        return self.last_state
614

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

    
618
    def get_current_state(self):
619
        today = date.today()
620
        return self.get_state_at_day(today)
621

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

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

    
638
    def get_states_history(self):
639
        return self.filestate_set.order_by('date_selected')
640

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

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

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

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

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

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

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

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

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

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

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

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

    
774
    def get_next_rdv(self):
775
        from views_utils import get_next_rdv
776
        return get_next_rdv(self)
777

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

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

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

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

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

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

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

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

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

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

    
960

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

    
970

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

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

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

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

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

    
1064
PatientRecord.DEFICIENCY_FIELDS = [field for field in PatientRecord._meta.get_all_field_names() if field.startswith('deficiency_')]
1065
PatientRecord.MISES_FIELDS = [field for field in PatientRecord._meta.get_all_field_names() if field.startswith('mises_')]
1066

    
(5-5/12)