Projet

Général

Profil

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

calebasse / calebasse / dossiers / models.py @ c6ef9ff5

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
DEFAULT_ACT_NUMBER_DIAGNOSTIC = 6
24
DEFAULT_ACT_NUMBER_TREATMENT = 30
25
DEFAULT_ACT_NUMBER_PROLONGATION = 10
26
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_DAYS = 0
27
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_MONTHS = 0
28
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_YEARS = 1
29

    
30
logger = logging.getLogger('calebasse.dossiers')
31

    
32

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

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

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

    
48

    
49
class HealthCare(models.Model):
50

    
51
    class Meta:
52
        app_label = 'dossiers'
53

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

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

    
72

    
73
class CmppHealthCareDiagnostic(HealthCare):
74

    
75
    class Meta:
76
        app_label = 'dossiers'
77

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

    
82
    def get_act_number(self):
83
        return self.act_number
84

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

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

    
98

    
99
class CmppHealthCareTreatment(HealthCare):
100

    
101
    class Meta:
102
        app_label = 'dossiers'
103

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

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

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

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

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

    
138
    def del_prolongation(self):
139
        pass
140

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

    
152

    
153
class SessadHealthCareNotification(HealthCare):
154

    
155
    class Meta:
156
        app_label = 'dossiers'
157

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

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

    
171
class ProtectionStatus(NamedAbstractModel):
172

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

    
178
class ProtectionState(models.Model):
179

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

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

    
194
    def __unicode__(self):
195
        return self.status.name + ' ' + str(self.start_date)
196

    
197
class Status(NamedAbstractModel):
198

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

    
204
    type = models.CharField(max_length=80)
205
    services = models.ManyToManyField('ressources.Service')
206

    
207

    
208
class FileState(models.Model):
209

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

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

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

    
235
    def save(self, **kwargs):
236
        self.date_selected = \
237
                datetime(self.date_selected.year,
238
                        self.date_selected.month, self.date_selected.day)
239
        super(FileState, self).save(**kwargs)
240

    
241
    def __unicode__(self):
242
        return self.status.name + ' ' + str(self.date_selected)
243

    
244
    def delete(self, *args, **kwargs):
245
        next_state = self.get_next_state()
246
        if next_state and self.previous_state:
247
            next_state.previous_state = self.previous_state
248
            next_state.save()
249
        if self.patient.last_state == self:
250
            self.patient.last_state = self.previous_state
251
            self.patient.save()
252
        super(FileState, self).delete(*args, **kwargs)
253

    
254
class PatientAddress(models.Model):
255

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

    
276
    def __unicode__(self):
277
        return self.display_name or u"Non renseigné"
278

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

    
295
class PatientContact(People):
296
    class Meta:
297
        verbose_name = u'Contact patient'
298
        verbose_name_plural = u'Contacts patient'
299

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

    
338
    addresses = models.ManyToManyField('PatientAddress', verbose_name=u"Adresses")
339
    contact_comment = models.TextField(verbose_name=u"Commentaire",
340
            null=True, blank=True)
341

    
342
    old_contact_id = models.CharField(max_length=256,
343
            verbose_name=u'Ancien ID du contact', blank=True, null=True)
344

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

    
368

    
369
class PatientRecordManager(models.Manager):
370
    def for_service(self, service):
371
        return self.filter(service=service)
372

    
373
class PatientRecord(ServiceLinkedAbstractModel, PatientContact):
374
    objects = PatientRecordManager()
375

    
376
    class Meta:
377
        verbose_name = u'Dossier'
378
        verbose_name_plural = u'Dossiers'
379

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

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

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

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

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

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

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

    
563
    old_id = models.CharField(max_length=256,
564
            verbose_name=u'Ancien ID', blank=True, null=True)
565
    old_old_id = models.CharField(max_length=256,
566
            verbose_name=u'Ancien ancien ID', blank=True, null=True)
567

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

    
573
    def get_state(self):
574
        return self.last_state
575

    
576
    def get_initial_state(self):
577
        return self.filestate_set.order_by('date_selected')[0]
578

    
579
    def get_current_state(self):
580
        today = date.today()
581
        return self.get_state_at_day(today)
582

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

    
593
    def was_in_state_at_day(self, date, status_type):
594
        state_at_day = self.get_state_at_day(date)
595
        if state_at_day and state_at_day.status.type == status_type:
596
            return True
597
        return False
598

    
599
    def get_states_history(self):
600
        return self.filestate_set.order_by('date_selected')
601

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

    
621
    def can_be_deleted(self):
622
        for act in self.act_set.all():
623
            if act.is_state('VALIDE'):
624
                return False
625
        return True
626

    
627
    def delete(self, *args, **kwargs):
628
        if self.can_be_deleted():
629
            super(PatientRecord, self).delete(*args, **kwargs)
630

    
631
    def get_ondisk_directory(self, service):
632
        if not settings.PATIENT_FILES_BASE_DIRECTORY:
633
            return None
634

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

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

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

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

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

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

    
721
    def remove_last_state(self):
722
        try:
723
            self.get_state().delete()
724
        except:
725
            pass
726

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

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

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

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

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

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

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

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

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

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

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

    
916

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

    
926

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

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

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

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

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