Projet

Général

Profil

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

calebasse / calebasse / dossiers / models.py @ a9520794

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

    
68
    def get_nb_acts_cared(self):
69
        return len(self.act_set.all())
70

    
71

    
72
class CmppHealthCareDiagnostic(HealthCare):
73
    act_number = models.IntegerField(default=DEFAULT_ACT_NUMBER_DIAGNOSTIC, verbose_name=u"Nombre d'actes couverts")
74
    end_date = models.DateField(verbose_name=u"Date de fin",
75
        blank=True, null=True)
76

    
77
    def get_act_number(self):
78
        return self.act_number
79

    
80
    def set_act_number(self, value):
81
        if value < self.get_nb_acts_cared():
82
            raise Exception("La valeur doit être supérieur au "
83
                "nombre d'actes déjà pris en charge")
84
        self.act_number = value
85
        self.save()
86

    
87
    def save(self, **kwargs):
88
        self.start_date = \
89
            datetime(self.start_date.year, self.start_date.month,
90
                self.start_date.day)
91
        super(CmppHealthCareDiagnostic, self).save(**kwargs)
92

    
93

    
94
class CmppHealthCareTreatment(HealthCare):
95
    act_number = models.IntegerField(default=DEFAULT_ACT_NUMBER_TREATMENT,
96
            verbose_name=u"Nombre d'actes couverts")
97
    end_date = models.DateField(verbose_name=u"Date de fin",
98
        blank=True, null=True)
99
    prolongation = models.IntegerField(default=0,
100
            verbose_name=u'Prolongation')
101
    prolongation_date = models.DateField(verbose_name=u"Date de prolongation",
102
        blank=True, null=True)
103

    
104
    def get_act_number(self):
105
        if self.is_extended():
106
            return self.act_number + self.prolongation
107
        return self.act_number
108

    
109
    def set_act_number(self, value):
110
        if value < self.get_nb_acts_cared():
111
            raise Exception("La valeur doit être supérieur au "
112
                "nombre d'actes déjà pris en charge")
113
        self.act_number = value
114
        self.save()
115

    
116
    def is_extended(self):
117
        if self.prolongation > 0:
118
            return True
119
        return False
120

    
121
    def add_prolongation(self, value=None):
122
        if not value:
123
            value = DEFAULT_ACT_NUMBER_PROLONGATION
124
        if self.is_extended():
125
            raise Exception(u'Prise en charge déja prolongée')
126
        self.prolongation = value
127
        self.save()
128

    
129
    def del_prolongation(self):
130
        pass
131

    
132
    def save(self, **kwargs):
133
        self.start_date = \
134
            datetime(self.start_date.year, self.start_date.month,
135
                self.start_date.day)
136
        if not self.end_date:
137
            self.end_date = self.start_date + \
138
                relativedelta(years=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_YEARS) + \
139
                relativedelta(months=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_MONTHS) + \
140
                relativedelta(days=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_DAYS-1)
141
        super(CmppHealthCareTreatment, self).save(**kwargs)
142

    
143

    
144
class SessadHealthCareNotification(HealthCare):
145
    end_date = models.DateField(verbose_name=u"Date de fin",
146
        blank=True, null=True)
147

    
148
    def save(self, **kwargs):
149
        self.start_date = \
150
            datetime(self.start_date.year, self.start_date.month,
151
                self.start_date.day)
152
        if self.end_date:
153
            self.end_date = \
154
                datetime(self.end_date.year, self.end_date.month,
155
                    self.end_date.day)
156
        super(SessadHealthCareNotification, self).save(**kwargs)
157

    
158
class ProtectionStatus(NamedAbstractModel):
159

    
160
    class Meta:
161
        verbose_name = u"Statut d'une mesure de protection"
162
        verbose_name_plural = u"Statuts d'une mesure de protection"
163

    
164
class ProtectionState(models.Model):
165

    
166
    class Meta:
167
        verbose_name = u'Mesure de protection du dossier patient'
168
        verbose_name_plural = u'Mesure de protections du dossier patient'
169
        ordering = ['-start_date']
170

    
171
    patient = models.ForeignKey('dossiers.PatientRecord',
172
        verbose_name=u'Dossier patient')
173
    status = models.ForeignKey('dossiers.ProtectionStatus', verbose_name=u'Statut de protection')
174
    created = models.DateTimeField(u'Création', auto_now_add=True)
175
    start_date = models.DateTimeField()
176
    end_date = models.DateTimeField(blank=True, null=True)
177
    comment = models.TextField(max_length=3000, blank=True, null=True)
178

    
179
    def __unicode__(self):
180
        return self.status.name + ' ' + str(self.start_date)
181

    
182
class Status(NamedAbstractModel):
183

    
184
    class Meta:
185
        verbose_name = u"Statut d'un état"
186
        verbose_name_plural = u"Statuts d'un état"
187

    
188
    type = models.CharField(max_length=80)
189
    services = models.ManyToManyField('ressources.Service')
190

    
191

    
192
class FileState(models.Model):
193

    
194
    class Meta:
195
        verbose_name = u'Etat du dossier patient'
196
        verbose_name_plural = u'Etats du dossier patient'
197

    
198
    patient = models.ForeignKey('dossiers.PatientRecord',
199
        verbose_name=u'Dossier patient')
200
    status = models.ForeignKey('dossiers.Status', verbose_name=u'Statut')
201
    created = models.DateTimeField(u'Création', auto_now_add=True)
202
    date_selected = models.DateTimeField()
203
    author = \
204
        models.ForeignKey(User,
205
        verbose_name=u'Auteur')
206
    comment = models.TextField(max_length=3000, blank=True, null=True)
207
    previous_state = models.ForeignKey('FileState',
208
            on_delete=models.SET_NULL,
209
            verbose_name=u'Etat précédent',
210
            blank=True, null=True)
211

    
212
    def get_next_state(self):
213
        try:
214
            return FileState.objects.get(previous_state=self)
215
        except:
216
            return None
217

    
218
    def save(self, **kwargs):
219
        self.date_selected = \
220
                datetime(self.date_selected.year,
221
                        self.date_selected.month, self.date_selected.day)
222
        super(FileState, self).save(**kwargs)
223

    
224
    def __unicode__(self):
225
        return self.status.name + ' ' + str(self.date_selected)
226

    
227
    def delete(self, *args, **kwargs):
228
        next_state = self.get_next_state()
229
        if next_state and self.previous_state:
230
            next_state.previous_state = self.previous_state
231
            next_state.save()
232
        if self.patient.last_state == self:
233
            self.patient.last_state = self.previous_state
234
            self.patient.save()
235
        obj_id = self.id
236
        super(FileState, self).delete(*args, **kwargs)
237

    
238
class PatientAddress(models.Model):
239

    
240
    display_name = models.CharField(max_length=276,
241
            verbose_name=u'Adresse complète', editable=False)
242
    phone = PhoneNumberField(verbose_name=u"Téléphone", blank=True, null=True)
243
    fax = PhoneNumberField(verbose_name=u"Fax", blank=True, null=True)
244
    place_of_life = models.BooleanField(verbose_name=u"Lieu de vie",
245
            default=True)
246
    number = models.CharField(max_length=12,
247
            verbose_name=u"Numéro", blank=True, null=True)
248
    recipient = models.CharField(max_length=100,
249
            verbose_name=u"Destinataire", blank=True, null=True)
250
    street = models.CharField(max_length=100,
251
            verbose_name=u"Rue", blank=True, null=True)
252
    address_complement = models.CharField(max_length=100,
253
            blank=True, null=True,
254
            verbose_name=u"Complément d'adresse")
255
    zip_code = ZipCodeField(verbose_name=u"Code postal", blank=True, null=True)
256
    city = models.CharField(max_length=60,
257
            verbose_name=u"Ville", blank=True, null=True)
258
    comment = models.TextField(verbose_name=u"Commentaire",
259
            null=True, blank=True)
260

    
261
    def __unicode__(self):
262
        return self.display_name or u"Non renseigné"
263

    
264
    def save(self, **kwargs):
265
        self.display_name = ''
266
        if self.recipient:
267
            self.display_name += self.recipient + ' '
268
        if self.number:
269
            self.display_name += self.number + ' '
270
        if self.street:
271
            self.display_name += self.street + ' '
272
        if self.address_complement:
273
            self.display_name += self.address_complement + ' '
274
        if self.zip_code:
275
            self.display_name += self.zip_code + ' '
276
        if self.city:
277
            self.display_name += self.city + ' '
278
        super(PatientAddress, self).save(**kwargs)
279

    
280
class PatientContact(People):
281
    class Meta:
282
        verbose_name = u'Contact patient'
283
        verbose_name_plural = u'Contacts patient'
284

    
285
    mobile = PhoneNumberField(verbose_name=u"Téléphone mobile", blank=True, null=True)
286
    # carte vitale
287
    social_security_id = models.CharField(max_length=13, verbose_name=u"NIR",
288
            null=True, blank=True)
289
    birthdate = models.DateField(verbose_name=u"Date de naissance",
290
            null=True, blank=True)
291
    birthplace = models.CharField(max_length=100, verbose_name=u"Lieu de naissance",
292
            null=True, blank=True)
293
    twinning_rank = models.IntegerField(verbose_name=u"Rang (gémellité)", default=1,
294
            validators=[MinValueValidator(1)])
295
    thirdparty_payer = models.BooleanField(verbose_name=u'Tiers-payant',
296
            default=False)
297
    begin_rights = models.DateField(verbose_name=u"Début de droits",
298
            null=True, blank=True)
299
    end_rights = models.DateField(verbose_name=u"Fin de droits",
300
            null=True, blank=True)
301
    health_center = models.ForeignKey('ressources.HealthCenter',
302
            verbose_name=u"Centre d'assurance maladie",
303
            null=True, blank=True)
304
    other_health_center = models.CharField(verbose_name=u"Centre spécifique",
305
            max_length=4,
306
            null=True, blank=True)
307
    type_of_contract = models.CharField(max_length=2,
308
            verbose_name=u"Type de contrat spécifique",
309
            choices=TYPE_OF_CONTRACT_CHOICES,
310
            null=True, blank=True)
311
    management_code = models.ForeignKey('ressources.ManagementCode',
312
            verbose_name=u"Code de gestion",
313
            null=True, blank=True)
314
    job = models.ForeignKey('ressources.Job',
315
            related_name="job",
316
            verbose_name=u"Profession",
317
            null=True, blank=True, default=None)
318
    parente = models.ForeignKey('ressources.PatientRelatedLink',
319
            verbose_name=u"Lien avec le patient (Parenté)",
320
            null=True, blank=True, default=None)
321
    ame = models.BooleanField(verbose_name=u"AME", default=False)
322

    
323
    addresses = models.ManyToManyField('PatientAddress', verbose_name=u"Adresses")
324
    contact_comment = models.TextField(verbose_name=u"Commentaire",
325
            null=True, blank=True)
326

    
327
    old_contact_id = models.CharField(max_length=256,
328
            verbose_name=u'Ancien ID du contact', blank=True, null=True)
329

    
330
    def get_control_key(self):
331
        if self.social_security_id:
332
            nir = self.social_security_id
333
            try:
334
                # Corse dpt 2A et 2B
335
                minus = 0
336
                if nir[6] in ('A', 'a'):
337
                    nir = [c for c in nir]
338
                    nir[6] = '0'
339
                    nir = ''.join(nir)
340
                    minus = 1000000
341
                elif nir[6] in ('B', 'b'):
342
                    nir = [c for c in nir]
343
                    nir[6] = '0'
344
                    nir = ''.join(nir)
345
                    minus = 2000000
346
                nir = int(nir) - minus
347
                return (97 - (nir % 97))
348
            except Exception, e:
349
                logger.warning("%s" % str(e))
350
                return None
351
        return None
352

    
353
    def age(self, age_format=None):
354
        if not self.birthdate:
355
            return 'inconnu'
356

    
357
        if not age_format:
358
            age_format = get_service_setting('age_format')
359

    
360
        now = datetime.today().date()
361
        age = relativedelta(now, self.birthdate)
362

    
363
        # by default we return the number of months for children < 2 years, but
364
        # there's a service setting to have it always displayed that way.
365
        months = age.years * 12 + age.months
366
        if months == 0:
367
            components = []
368
        elif age.years < 2 or age_format == 'months_only':
369
            components = ['%s mois' % months]
370
        else:
371
            components = ['%s ans' % age.years]
372
            if age.months:
373
                components.append('%s mois' % age.months)
374

    
375
        # under three months, we also display the number of days
376
        if months < 3:
377
            if age.days == 1:
378
                components.append("%s jour" % age.days)
379
            elif age.days > 1:
380
                components.append('%s jours' % age.days)
381

    
382
        return ' et '.join(components)
383

    
384

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

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

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

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

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

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

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

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

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

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

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

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

    
595
    def get_state(self):
596
        return self.last_state
597

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

    
601
    def get_current_state(self):
602
        today = date.today()
603
        return self.get_state_at_day(today)
604

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

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

    
621
    def get_states_history(self):
622
        return self.filestate_set.order_by('date_selected')
623

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

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

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

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

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

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

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

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

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

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

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

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

    
757
    def get_next_rdv(self):
758
        from views_utils import get_next_rdv
759
        return get_next_rdv(self)
760

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

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

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

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

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

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

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

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

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

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

    
943

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

    
953

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

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

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

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

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